tokmon 0.3.0 → 0.3.2

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 +85 -40
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/app.tsx
7
- import { useState, useEffect } from "react";
8
- import { Box, Text, useInput, useStdout } from "ink";
7
+ import { useState, useEffect, useRef } from "react";
8
+ import { Box, Text, useInput, useStdout, useApp } from "ink";
9
9
 
10
10
  // src/data.ts
11
11
  import { readdir, stat as fsStat } from "fs/promises";
@@ -194,15 +194,14 @@ function isoWeekLabel(ts) {
194
194
  function monthLabel(ts) {
195
195
  return new Date(ts).toISOString().slice(0, 7);
196
196
  }
197
- async function fetchData() {
197
+ async function fetchDashboard() {
198
198
  const now = Date.now();
199
199
  const d = /* @__PURE__ */ new Date();
200
- const lookback = new Date(d.getFullYear(), d.getMonth() - 6, 1).getTime();
201
200
  const monthStart = new Date(d.getFullYear(), d.getMonth(), 1).getTime();
202
201
  const todayStart = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
203
202
  const weekDay = d.getDay();
204
203
  const weekStart = new Date(d.getFullYear(), d.getMonth(), d.getDate() - (weekDay === 0 ? 6 : weekDay - 1)).getTime();
205
- const entries = await loadEntries(lookback);
204
+ const entries = await loadEntries(monthStart);
206
205
  const fiveHoursAgo = now - 5 * 36e5;
207
206
  const blockEntries = entries.filter((e) => e.ts >= fiveHoursAgo);
208
207
  let block = null;
@@ -215,17 +214,21 @@ async function fetchData() {
215
214
  const percent = Math.min(100, (now - oldest) / (5 * 36e5) * 100);
216
215
  block = { spent, projected: burnRate * 5, burnRate, percent, remaining: minutes(remainMs / 6e4) };
217
216
  }
218
- const daily = groupBy(entries, (e) => new Date(e.ts).toISOString().slice(0, 10));
219
- const weekly = groupBy(entries, (e) => isoWeekLabel(e.ts));
220
- const monthly = groupBy(entries, (e) => monthLabel(e.ts));
221
217
  return {
222
218
  today: sum(entries.filter((e) => e.ts >= todayStart)),
223
219
  week: sum(entries.filter((e) => e.ts >= weekStart)),
224
220
  month: sum(entries.filter((e) => e.ts >= monthStart)),
225
- block,
226
- daily,
227
- weekly,
228
- monthly
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))
229
232
  };
230
233
  }
231
234
 
@@ -267,7 +270,9 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
267
270
  var TABS = ["Dashboard", "Table"];
268
271
  var VIEWS = ["Daily", "Weekly", "Monthly"];
269
272
  function App({ interval: cliInterval }) {
270
- const [data, setData] = useState(null);
273
+ const [dashboard, setDashboard] = useState(null);
274
+ const [table, setTable] = useState(null);
275
+ const [tableLoading, setTableLoading] = useState(false);
271
276
  const [error, setError] = useState(null);
272
277
  const [updated, setUpdated] = useState(/* @__PURE__ */ new Date());
273
278
  const [tab, setTab] = useState(0);
@@ -276,6 +281,7 @@ function App({ interval: cliInterval }) {
276
281
  const [showSettings, setShowSettings] = useState(false);
277
282
  const [config, setConfig] = useState(null);
278
283
  const [settingsCursor, setSettingsCursor] = useState(0);
284
+ const tableLoadedOnce = useRef(false);
279
285
  const { stdout } = useStdout();
280
286
  const rows = stdout?.rows ?? 24;
281
287
  const cols = stdout?.columns ?? 80;
@@ -287,6 +293,61 @@ function App({ interval: cliInterval }) {
287
293
  if (c.clearScreen && stdout) stdout.write("\x1B[2J\x1B[H");
288
294
  });
289
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]);
350
+ const { exit } = useApp();
290
351
  const isTTY = process.stdin.isTTY === true;
291
352
  const settingsItems = 2;
292
353
  const cfg = config ?? { interval: 2, clearScreen: true };
@@ -320,6 +381,10 @@ function App({ interval: cliInterval }) {
320
381
  }
321
382
  return;
322
383
  }
384
+ if (input === "q") {
385
+ exit();
386
+ return;
387
+ }
323
388
  if (input === "s") {
324
389
  setShowSettings(true);
325
390
  return;
@@ -377,30 +442,9 @@ function App({ interval: cliInterval }) {
377
442
  if (key.pageDown) setScroll((s) => s + Math.max(1, rows - 12));
378
443
  if (key.pageUp) setScroll((s) => Math.max(0, s - Math.max(1, rows - 12)));
379
444
  }, { isActive: isTTY });
380
- useEffect(() => {
381
- let active = true;
382
- const load = async () => {
383
- try {
384
- const result = await fetchData();
385
- if (active) {
386
- setData(result);
387
- setError(null);
388
- setUpdated(/* @__PURE__ */ new Date());
389
- }
390
- } catch (e) {
391
- if (active) setError(e instanceof Error ? e.message : String(e));
392
- }
393
- };
394
- load();
395
- const id = setInterval(load, interval2);
396
- return () => {
397
- active = false;
398
- clearInterval(id);
399
- };
400
- }, [interval2]);
401
445
  if (error) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: error }) });
402
- if (!data) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading..." }) });
403
- const tableData = [data.daily, data.weekly, data.monthly][view];
446
+ if (!dashboard) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading..." }) });
447
+ const tableData = table ? [table.daily, table.weekly, table.monthly][view] : [];
404
448
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
405
449
  /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
406
450
  /* @__PURE__ */ jsxs(Box, { children: [
@@ -419,14 +463,14 @@ function App({ interval: cliInterval }) {
419
463
  showSettings ? /* @__PURE__ */ jsx(SettingsView, { config: cfg, cursor: settingsCursor }) : /* @__PURE__ */ jsxs(Fragment, { children: [
420
464
  /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
421
465
  /* @__PURE__ */ jsx(TabBar, { tabs: TABS, active: tab }),
422
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab s=settings" })
466
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab/\u2190\u2192" })
423
467
  ] }),
424
468
  /* @__PURE__ */ jsx(Box, { height: 1 }),
425
- tab === 0 && /* @__PURE__ */ jsx(DashboardView, { data }),
469
+ tab === 0 && /* @__PURE__ */ jsx(DashboardView, { data: dashboard }),
426
470
  tab === 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
427
471
  /* @__PURE__ */ jsx(ViewBar, { views: VIEWS, active: view }),
428
472
  /* @__PURE__ */ jsx(Box, { height: 1 }),
429
- /* @__PURE__ */ jsx(TableView, { rows: tableData, scroll, maxRows: rows - 12, wide: cols > 90 })
473
+ 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 })
430
474
  ] })
431
475
  ] }),
432
476
  /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
@@ -434,7 +478,8 @@ function App({ interval: cliInterval }) {
434
478
  /* @__PURE__ */ jsx(Text, { children: "David Ilie" }),
435
479
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (" }),
436
480
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: "davidilie.com" }),
437
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ")" })
481
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ") \xB7 " }),
482
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "s=settings q=quit" })
438
483
  ] })
439
484
  ] });
440
485
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Terminal dashboard for Claude Code usage and costs",
5
5
  "type": "module",
6
6
  "bin": {