tokmon 0.14.0 → 0.14.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 +199 -70
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1987,11 +1987,15 @@ var copilotProvider = {
1987
1987
  };
1988
1988
 
1989
1989
  // src/providers/antigravity/billing.ts
1990
- import { access as access7, readdir as readdir6 } from "fs/promises";
1991
- import { join as join12 } from "path";
1992
- import { homedir as homedir10 } from "os";
1990
+ import { access as access7, readdir as readdir7 } from "fs/promises";
1991
+ import { join as join13 } from "path";
1992
+ import { homedir as homedir11 } from "os";
1993
1993
 
1994
1994
  // src/providers/cloud-code.ts
1995
+ import { readFile as readFile6, readdir as readdir6, realpath, stat } from "fs/promises";
1996
+ import { spawnSync } from "child_process";
1997
+ import { homedir as homedir10 } from "os";
1998
+ import { dirname, join as join12 } from "path";
1995
1999
  var CLOUD_CODE_URLS = [
1996
2000
  "https://daily-cloudcode-pa.googleapis.com",
1997
2001
  "https://cloudcode-pa.googleapis.com"
@@ -2000,8 +2004,9 @@ var LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist";
2000
2004
  var FETCH_MODELS_PATH = "/v1internal:fetchAvailableModels";
2001
2005
  var RETRIEVE_QUOTA_PATH = "/v1internal:retrieveUserQuota";
2002
2006
  var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
2003
- var GOOGLE_CLIENT_ID = process.env.TOKMON_GOOGLE_CLIENT_ID ?? "";
2004
- var GOOGLE_CLIENT_SECRET = process.env.TOKMON_GOOGLE_CLIENT_SECRET ?? "";
2007
+ var GOOGLE_OAUTH_CLIENT_REGEX = /OAUTH_CLIENT_ID\s*=\s*["']([0-9]{6,}-[a-z0-9]+\.apps\.googleusercontent\.com)["']\s*;?\s*(?:var|const|let)?\s*OAUTH_CLIENT_SECRET\s*=\s*["'](GOCSPX-[A-Za-z0-9_-]+)["']/s;
2008
+ var MAX_BUNDLE_READ = 32 * 1024 * 1024;
2009
+ var cachedClient;
2005
2010
  var OAUTH_TOKEN_KEY = "antigravityUnifiedStateSync.oauthToken";
2006
2011
  var OAUTH_TOKEN_SENTINEL = "oauthTokenInfoSentinelKey";
2007
2012
  var CC_MODEL_BLACKLIST = {
@@ -2112,11 +2117,90 @@ function redact(token) {
2112
2117
  if (!token) return "none";
2113
2118
  return `...${token.slice(-4)}`;
2114
2119
  }
2120
+ function geminiBundleCandidates() {
2121
+ const candidates = [];
2122
+ const addBundle = (nodeModulesRoot) => {
2123
+ if (!nodeModulesRoot) return;
2124
+ candidates.push(join12(nodeModulesRoot, "@google", "gemini-cli", "bundle"));
2125
+ };
2126
+ try {
2127
+ const which = spawnSync("command", ["-v", "gemini"], { encoding: "utf8", timeout: 5e3 });
2128
+ const resolved = typeof which.stdout === "string" ? which.stdout.trim().split("\n")[0]?.trim() : "";
2129
+ if (resolved) candidates.push(resolved);
2130
+ } catch {
2131
+ }
2132
+ const home = homedir10();
2133
+ addBundle("/opt/homebrew/lib/node_modules");
2134
+ addBundle("/usr/local/lib/node_modules");
2135
+ addBundle(join12(home, ".local", "share", "node_modules"));
2136
+ addBundle(join12(home, ".bun", "install", "global", "node_modules"));
2137
+ try {
2138
+ const prefix = spawnSync("npm", ["config", "get", "prefix"], { encoding: "utf8", timeout: 5e3 });
2139
+ const root = typeof prefix.stdout === "string" ? prefix.stdout.trim() : "";
2140
+ if (root && root !== "undefined") addBundle(join12(root, "lib", "node_modules"));
2141
+ } catch {
2142
+ }
2143
+ return [...new Set(candidates.filter(Boolean))];
2144
+ }
2145
+ async function resolveBundleDir(candidate) {
2146
+ try {
2147
+ if (candidate.endsWith(`${join12("@google", "gemini-cli", "bundle")}`)) {
2148
+ return candidate;
2149
+ }
2150
+ const real = await realpath(candidate);
2151
+ return dirname(real);
2152
+ } catch {
2153
+ return null;
2154
+ }
2155
+ }
2156
+ async function scanBundleDir(dir) {
2157
+ let entries;
2158
+ try {
2159
+ entries = await readdir6(dir);
2160
+ } catch {
2161
+ return null;
2162
+ }
2163
+ const targets = entries.filter((name) => name === "gemini.js" || name.startsWith("chunk-") && name.endsWith(".js"));
2164
+ for (const name of targets) {
2165
+ const filePath = join12(dir, name);
2166
+ try {
2167
+ const info = await stat(filePath);
2168
+ if (!info.isFile() || info.size > MAX_BUNDLE_READ) continue;
2169
+ const contents = await readFile6(filePath, "utf8");
2170
+ if (!contents.includes("OAUTH_CLIENT_SECRET")) continue;
2171
+ const match = GOOGLE_OAUTH_CLIENT_REGEX.exec(contents);
2172
+ if (match) return { clientId: match[1], clientSecret: match[2] };
2173
+ } catch {
2174
+ }
2175
+ }
2176
+ return null;
2177
+ }
2178
+ async function discoverGoogleOAuthClient() {
2179
+ try {
2180
+ for (const candidate of geminiBundleCandidates()) {
2181
+ const dir = await resolveBundleDir(candidate);
2182
+ if (!dir) continue;
2183
+ const found = await scanBundleDir(dir);
2184
+ if (found) return found;
2185
+ }
2186
+ } catch {
2187
+ }
2188
+ return null;
2189
+ }
2190
+ async function resolveGoogleClient() {
2191
+ const envId = process.env.TOKMON_GOOGLE_CLIENT_ID?.trim();
2192
+ const envSecret = process.env.TOKMON_GOOGLE_CLIENT_SECRET?.trim();
2193
+ if (envId && envSecret) return { clientId: envId, clientSecret: envSecret };
2194
+ if (cachedClient === void 0) cachedClient = await discoverGoogleOAuthClient();
2195
+ return cachedClient;
2196
+ }
2115
2197
  async function refreshAccessToken(refreshToken) {
2116
- if (!refreshToken || !GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) return null;
2198
+ if (!refreshToken) return null;
2199
+ const client = await resolveGoogleClient();
2200
+ if (!client) return null;
2117
2201
  const body = new URLSearchParams({
2118
- client_id: GOOGLE_CLIENT_ID,
2119
- client_secret: GOOGLE_CLIENT_SECRET,
2202
+ client_id: client.clientId,
2203
+ client_secret: client.clientSecret,
2120
2204
  refresh_token: refreshToken,
2121
2205
  grant_type: "refresh_token"
2122
2206
  });
@@ -2289,33 +2373,33 @@ async function firstExisting(paths) {
2289
2373
  return paths[0];
2290
2374
  }
2291
2375
  async function antigravityStateDb(homeDir) {
2292
- const base = homeDir ?? homedir10();
2376
+ const base = homeDir ?? homedir11();
2293
2377
  const tail = ["User", "globalStorage", "state.vscdb"];
2294
2378
  if (process.platform === "darwin") {
2295
- const support = join12(base, "Library", "Application Support");
2379
+ const support = join13(base, "Library", "Application Support");
2296
2380
  const exact = [
2297
- join12(support, "Antigravity IDE", ...tail),
2298
- join12(support, "Antigravity", ...tail)
2381
+ join13(support, "Antigravity IDE", ...tail),
2382
+ join13(support, "Antigravity", ...tail)
2299
2383
  ];
2300
2384
  try {
2301
- const entries = await readdir6(support, { withFileTypes: true });
2302
- const matches = entries.filter((e) => e.isDirectory() && e.name.includes("Antigravity")).map((e) => join12(support, e.name, ...tail));
2385
+ const entries = await readdir7(support, { withFileTypes: true });
2386
+ const matches = entries.filter((e) => e.isDirectory() && e.name.includes("Antigravity")).map((e) => join13(support, e.name, ...tail));
2303
2387
  return firstExisting([...exact, ...matches]);
2304
2388
  } catch {
2305
2389
  return firstExisting(exact);
2306
2390
  }
2307
2391
  }
2308
2392
  if (process.platform === "win32") {
2309
- const roaming = homeDir ? join12(homeDir, "AppData", "Roaming") : envDir("APPDATA") ?? join12(base, "AppData", "Roaming");
2393
+ const roaming = homeDir ? join13(homeDir, "AppData", "Roaming") : envDir("APPDATA") ?? join13(base, "AppData", "Roaming");
2310
2394
  return firstExisting([
2311
- join12(roaming, "Antigravity IDE", ...tail),
2312
- join12(roaming, "Antigravity", ...tail)
2395
+ join13(roaming, "Antigravity IDE", ...tail),
2396
+ join13(roaming, "Antigravity", ...tail)
2313
2397
  ]);
2314
2398
  }
2315
- const cfg = homeDir ? join12(homeDir, ".config") : envDir("XDG_CONFIG_HOME") ?? join12(base, ".config");
2399
+ const cfg = homeDir ? join13(homeDir, ".config") : envDir("XDG_CONFIG_HOME") ?? join13(base, ".config");
2316
2400
  return firstExisting([
2317
- join12(cfg, "Antigravity IDE", ...tail),
2318
- join12(cfg, "Antigravity", ...tail)
2401
+ join13(cfg, "Antigravity IDE", ...tail),
2402
+ join13(cfg, "Antigravity", ...tail)
2319
2403
  ]);
2320
2404
  }
2321
2405
  async function detectAntigravity(homeDir) {
@@ -2346,11 +2430,11 @@ var antigravityProvider = {
2346
2430
  };
2347
2431
 
2348
2432
  // src/providers/gemini/billing.ts
2349
- import { access as access8, readFile as readFile6 } from "fs/promises";
2350
- import { join as join13 } from "path";
2351
- import { homedir as homedir11 } from "os";
2433
+ import { access as access8, readFile as readFile7 } from "fs/promises";
2434
+ import { join as join14 } from "path";
2435
+ import { homedir as homedir12 } from "os";
2352
2436
  function geminiCredsPath(homeDir) {
2353
- return join13(homeDir ?? homedir11(), ".gemini", "oauth_creds.json");
2437
+ return join14(homeDir ?? homedir12(), ".gemini", "oauth_creds.json");
2354
2438
  }
2355
2439
  async function detectGemini(homeDir) {
2356
2440
  try {
@@ -2362,7 +2446,7 @@ async function detectGemini(homeDir) {
2362
2446
  }
2363
2447
  async function readGeminiCreds(path) {
2364
2448
  try {
2365
- return JSON.parse(await readFile6(path, "utf8"));
2449
+ return JSON.parse(await readFile7(path, "utf8"));
2366
2450
  } catch {
2367
2451
  return null;
2368
2452
  }
@@ -2398,26 +2482,26 @@ var geminiProvider = {
2398
2482
 
2399
2483
  // src/providers/detect.ts
2400
2484
  import { accessSync, constants } from "fs";
2401
- import { join as join14, delimiter, isAbsolute as isAbsolute3 } from "path";
2402
- import { homedir as homedir12 } from "os";
2485
+ import { join as join15, delimiter, isAbsolute as isAbsolute3 } from "path";
2486
+ import { homedir as homedir13 } from "os";
2403
2487
  function searchDirs() {
2404
- const home = homedir12();
2488
+ const home = homedir13();
2405
2489
  const fromEnv = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
2406
2490
  const extra = process.platform === "win32" ? [
2407
- process.env.APPDATA && join14(process.env.APPDATA, "npm"),
2408
- process.env.LOCALAPPDATA && join14(process.env.LOCALAPPDATA, "pnpm"),
2409
- join14(home, "scoop", "shims")
2491
+ process.env.APPDATA && join15(process.env.APPDATA, "npm"),
2492
+ process.env.LOCALAPPDATA && join15(process.env.LOCALAPPDATA, "pnpm"),
2493
+ join15(home, "scoop", "shims")
2410
2494
  ] : [
2411
2495
  "/opt/homebrew/bin",
2412
2496
  "/usr/local/bin",
2413
2497
  "/usr/bin",
2414
2498
  "/bin",
2415
2499
  "/opt/local/bin",
2416
- join14(home, ".local", "bin"),
2417
- join14(home, "bin"),
2418
- join14(home, ".npm-global", "bin"),
2419
- join14(home, ".bun", "bin"),
2420
- join14(home, ".local", "share", "pnpm")
2500
+ join15(home, ".local", "bin"),
2501
+ join15(home, "bin"),
2502
+ join15(home, ".npm-global", "bin"),
2503
+ join15(home, ".bun", "bin"),
2504
+ join15(home, ".local", "share", "pnpm")
2421
2505
  ];
2422
2506
  return [.../* @__PURE__ */ new Set([...fromEnv, ...extra.filter((d) => !!d)])];
2423
2507
  }
@@ -2434,7 +2518,7 @@ function onPath(names) {
2434
2518
  for (const dir of searchDirs()) {
2435
2519
  for (const n of names) {
2436
2520
  for (const e of exts) {
2437
- if (isExec(join14(dir, n + e))) return true;
2521
+ if (isExec(join15(dir, n + e))) return true;
2438
2522
  }
2439
2523
  }
2440
2524
  }
@@ -2444,7 +2528,7 @@ function anyExists(paths) {
2444
2528
  return paths.some((p) => !!p && isExec(p));
2445
2529
  }
2446
2530
  function installSignals(id) {
2447
- const home = homedir12();
2531
+ const home = homedir13();
2448
2532
  const pf = process.env.ProgramFiles;
2449
2533
  const pf86 = process.env["ProgramFiles(x86)"];
2450
2534
  const lad = process.env.LOCALAPPDATA;
@@ -2452,8 +2536,8 @@ function installSignals(id) {
2452
2536
  case "claude":
2453
2537
  return onPath(["claude"]) || anyExists([
2454
2538
  "/Applications/Claude.app",
2455
- join14(home, "Applications", "Claude.app"),
2456
- lad && join14(lad, "Programs", "claude", "Claude.exe")
2539
+ join15(home, "Applications", "Claude.app"),
2540
+ lad && join15(lad, "Programs", "claude", "Claude.exe")
2457
2541
  ]);
2458
2542
  case "codex": {
2459
2543
  const bin = process.env.CODEX_BIN;
@@ -2463,10 +2547,10 @@ function installSignals(id) {
2463
2547
  case "cursor":
2464
2548
  return onPath(["cursor", "cursor-agent"]) || anyExists([
2465
2549
  "/Applications/Cursor.app",
2466
- join14(home, "Applications", "Cursor.app"),
2467
- lad && join14(lad, "Programs", "cursor", "Cursor.exe"),
2468
- pf && join14(pf, "Cursor", "Cursor.exe"),
2469
- pf86 && join14(pf86, "Cursor", "Cursor.exe"),
2550
+ join15(home, "Applications", "Cursor.app"),
2551
+ lad && join15(lad, "Programs", "cursor", "Cursor.exe"),
2552
+ pf && join15(pf, "Cursor", "Cursor.exe"),
2553
+ pf86 && join15(pf86, "Cursor", "Cursor.exe"),
2470
2554
  "/opt/Cursor/cursor",
2471
2555
  "/usr/share/cursor/cursor",
2472
2556
  "/usr/bin/cursor"
@@ -2480,8 +2564,8 @@ function installSignals(id) {
2480
2564
  case "antigravity":
2481
2565
  return onPath(["antigravity"]) || anyExists([
2482
2566
  "/Applications/Antigravity.app",
2483
- join14(home, "Applications", "Antigravity.app"),
2484
- lad && join14(lad, "Programs", "Antigravity", "Antigravity.exe")
2567
+ join15(home, "Applications", "Antigravity.app"),
2568
+ lad && join15(lad, "Programs", "Antigravity", "Antigravity.exe")
2485
2569
  ]);
2486
2570
  case "gemini":
2487
2571
  return onPath(["gemini"]);
@@ -2550,14 +2634,14 @@ function accountsByProvider(accounts) {
2550
2634
  }
2551
2635
 
2552
2636
  // src/snapshot.ts
2553
- import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir3, rename as rename3 } from "fs/promises";
2554
- import { join as join15 } from "path";
2637
+ import { readFile as readFile8, writeFile as writeFile3, mkdir as mkdir3, rename as rename3 } from "fs/promises";
2638
+ import { join as join16 } from "path";
2555
2639
  function snapshotFile() {
2556
- return join15(cacheDir(), "dashboard-snapshot.json");
2640
+ return join16(cacheDir(), "dashboard-snapshot.json");
2557
2641
  }
2558
2642
  async function loadSnapshot() {
2559
2643
  try {
2560
- const obj = JSON.parse(await readFile7(snapshotFile(), "utf-8"));
2644
+ const obj = JSON.parse(await readFile8(snapshotFile(), "utf-8"));
2561
2645
  return obj && typeof obj === "object" ? obj : {};
2562
2646
  } catch {
2563
2647
  return {};
@@ -2573,7 +2657,7 @@ function saveSnapshot(stats) {
2573
2657
  try {
2574
2658
  const dir = cacheDir();
2575
2659
  await mkdir3(dir, { recursive: true });
2576
- const tmp = join15(dir, `dashboard-snapshot.json.${process.pid}.tmp`);
2660
+ const tmp = join16(dir, `dashboard-snapshot.json.${process.pid}.tmp`);
2577
2661
  await writeFile3(tmp, JSON.stringify(obj));
2578
2662
  await rename3(tmp, snapshotFile());
2579
2663
  } catch {
@@ -2930,6 +3014,30 @@ function aggregate(list) {
2930
3014
  }
2931
3015
  return z;
2932
3016
  }
3017
+ function TotalsRow({ groups, stats, cols }) {
3018
+ const zero = () => ({ cost: 0, tokens: 0, cacheRead: 0, cacheSavings: 0 });
3019
+ const t = zero(), w = zero(), m = zero();
3020
+ for (const g of groups) {
3021
+ if (!PROVIDERS[g.provider].hasUsage) continue;
3022
+ for (const a of g.accounts) {
3023
+ const d = stats.get(a.id)?.dashboard;
3024
+ if (!d) continue;
3025
+ t.cost += d.today.cost;
3026
+ t.tokens += d.today.tokens;
3027
+ w.cost += d.week.cost;
3028
+ w.tokens += d.week.tokens;
3029
+ m.cost += d.month.cost;
3030
+ m.tokens += d.month.tokens;
3031
+ }
3032
+ }
3033
+ const inner = cols - 4;
3034
+ const dot = glyphs().middot;
3035
+ const full = `${glyphs().dotAll} Today ${currency(t.cost)} (${tokens(t.tokens)} tok) ${dot} Week ${currency(w.cost)} (${tokens(w.tokens)} tok) ${dot} Month ${currency(m.cost)} (${tokens(m.tokens)} tok)`;
3036
+ const noTok = `${glyphs().dotAll} Today ${currency(t.cost)} ${dot} Week ${currency(w.cost)} ${dot} Month ${currency(m.cost)}`;
3037
+ const tight = `${glyphs().dotAll} ${currency(t.cost)} ${dot} ${currency(w.cost)} ${dot} ${currency(m.cost)}`;
3038
+ const text = full.length <= inner ? full : noTok.length <= inner ? noTok : tight;
3039
+ return /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: text }) });
3040
+ }
2933
3041
 
2934
3042
  // src/ui/table.tsx
2935
3043
  import { Box as Box3, Text as Text3 } from "ink";
@@ -3778,6 +3886,7 @@ var IS_TTY = process.stdin.isTTY === true;
3778
3886
  var DEBOUNCE_MS = 300;
3779
3887
  var LOADER_GRACE_MS = 600;
3780
3888
  var LOADER_MAX_MS = 8e3;
3889
+ var LOADER_MIN_VISIBLE_MS = 700;
3781
3890
  var DEFAULT_CONFIG = {
3782
3891
  interval: 2,
3783
3892
  billingInterval: 5,
@@ -3792,8 +3901,13 @@ var DEFAULT_CONFIG = {
3792
3901
  ascii: "auto",
3793
3902
  knownProviders: []
3794
3903
  };
3795
- function App({ interval: cliInterval }) {
3796
- const [config2, setConfig] = useState3(null);
3904
+ function applyStartup(c, cliInterval) {
3905
+ if (cliInterval) c = { ...c, interval: cliInterval / 1e3 };
3906
+ if (c.defaultFocus === "all") c = { ...c, activeAccountId: null };
3907
+ return c;
3908
+ }
3909
+ function App({ interval: cliInterval, initialConfig }) {
3910
+ const [config2, setConfig] = useState3(() => initialConfig ? applyStartup(initialConfig, cliInterval) : null);
3797
3911
  const [detected, setDetected] = useState3([]);
3798
3912
  const [stats, setStats] = useState3(/* @__PURE__ */ new Map());
3799
3913
  const [peak, setPeak] = useState3(null);
@@ -3820,7 +3934,9 @@ function App({ interval: cliInterval }) {
3820
3934
  const [dashPage, setDashPage] = useState3(0);
3821
3935
  const [debouncePassed, setDebouncePassed] = useState3(false);
3822
3936
  const [graceHold, setGraceHold] = useState3(false);
3937
+ const [loaderShownAt, setLoaderShownAt] = useState3(null);
3823
3938
  const loaderDone = useRef2(false);
3939
+ const prevShowPicker = useRef2(false);
3824
3940
  const { stdout } = useStdout();
3825
3941
  const { exit } = useApp();
3826
3942
  const rows = stdout?.rows ?? 24;
@@ -3858,7 +3974,7 @@ function App({ interval: cliInterval }) {
3858
3974
  /*"focus "*/
3859
3975
  ))) : 0;
3860
3976
  const headerRows = cols < 70 ? 2 : 1;
3861
- const CHROME = 2 + headerRows + 3 + (hasStrip ? 1 + stripLines : 0) + 2;
3977
+ const CHROME = 2 + headerRows + 3 + (hasStrip ? 1 + stripLines : 0) + 2 + 2;
3862
3978
  const gridBudget = Math.max(1, rows - CHROME);
3863
3979
  const dashLayout = chooseLayout(
3864
3980
  Math.max(56, cols - 4),
@@ -3878,7 +3994,8 @@ function App({ interval: cliInterval }) {
3878
3994
  const needsOnboarding = configReady && !cfg.onboarded;
3879
3995
  const newProviders = configReady && cfg.onboarded ? PROVIDER_ORDER.filter((p) => !cfg.knownProviders.includes(p) && detected.includes(p)) : [];
3880
3996
  const showPicker = needsOnboarding || newProviders.length > 0;
3881
- const showLoader = configReady && !showPicker && !showSettings && !TOO_SMALL && accounts.length > 0 && (!allReady || graceHold) && debouncePassed && !loaderDone.current;
3997
+ const minVisibleHold = loaderShownAt !== null && Date.now() - loaderShownAt < LOADER_MIN_VISIBLE_MS;
3998
+ const showLoader = configReady && !showPicker && !showSettings && !TOO_SMALL && accounts.length > 0 && (!allReady || graceHold || minVisibleHold) && (debouncePassed || loaderShownAt !== null) && !loaderDone.current;
3882
3999
  const pickerProviders = needsOnboarding ? PROVIDER_ORDER : newProviders;
3883
4000
  const onboardEnabled = onboardSel ?? detected;
3884
4001
  const onboardItems = pickerProviders.map((pid) => ({
@@ -3889,15 +4006,24 @@ function App({ interval: cliInterval }) {
3889
4006
  enabled: onboardEnabled.includes(pid)
3890
4007
  }));
3891
4008
  useEffect3(() => {
3892
- loadConfig().then((c) => {
3893
- if (cliInterval) c = { ...c, interval: cliInterval / 1e3 };
3894
- if (c.defaultFocus === "all") c = { ...c, activeAccountId: null };
3895
- setConfig(c);
3896
- });
4009
+ const wasPicker = prevShowPicker.current;
4010
+ prevShowPicker.current = showPicker;
4011
+ if (wasPicker && !showPicker) {
4012
+ loaderDone.current = false;
4013
+ setDebouncePassed(false);
4014
+ setGraceHold(false);
4015
+ setLoaderShownAt(null);
4016
+ }
4017
+ }, [showPicker]);
4018
+ useEffect3(() => {
4019
+ if (showLoader && loaderShownAt === null) setLoaderShownAt(Date.now());
4020
+ }, [showLoader, loaderShownAt]);
4021
+ useEffect3(() => {
4022
+ if (!initialConfig) loadConfig().then((c) => setConfig(applyStartup(c, cliInterval)));
3897
4023
  detectProviders().then(setDetected);
3898
4024
  }, []);
3899
4025
  useEffect3(() => {
3900
- if (seededRef.current || !configReady || accounts.length === 0) return;
4026
+ if (seededRef.current || !configReady || showPicker || accounts.length === 0) return;
3901
4027
  seededRef.current = true;
3902
4028
  loadSnapshot().then((snap) => {
3903
4029
  setStats((prev) => {
@@ -3910,7 +4036,7 @@ function App({ interval: cliInterval }) {
3910
4036
  return next;
3911
4037
  });
3912
4038
  });
3913
- }, [configReady, accountsKey]);
4039
+ }, [configReady, showPicker, accountsKey]);
3914
4040
  useEffect3(() => {
3915
4041
  if (stats.size === 0) return;
3916
4042
  const t = setTimeout(() => saveSnapshot(stats), 500);
@@ -3931,19 +4057,21 @@ function App({ interval: cliInterval }) {
3931
4057
  }, [configReady, showPicker, accountsKey]);
3932
4058
  useEffect3(() => {
3933
4059
  if (!allReady || loaderDone.current) return;
3934
- if (!debouncePassed) {
4060
+ if (loaderShownAt === null) {
3935
4061
  loaderDone.current = true;
3936
4062
  return;
3937
4063
  }
3938
4064
  setGraceHold(true);
4065
+ const minRemaining = Math.max(0, LOADER_MIN_VISIBLE_MS - (Date.now() - loaderShownAt));
4066
+ const hold = Math.max(LOADER_GRACE_MS, minRemaining);
3939
4067
  const t = setTimeout(() => {
3940
4068
  loaderDone.current = true;
3941
4069
  setGraceHold(false);
3942
- }, LOADER_GRACE_MS);
4070
+ }, hold);
3943
4071
  return () => clearTimeout(t);
3944
4072
  }, [allReady]);
3945
4073
  useEffect3(() => {
3946
- if (!configReady) return;
4074
+ if (!configReady || showPicker) return;
3947
4075
  let active2 = true;
3948
4076
  let timer;
3949
4077
  const load = async () => {
@@ -3970,9 +4098,9 @@ function App({ interval: cliInterval }) {
3970
4098
  active2 = false;
3971
4099
  clearTimeout(timer);
3972
4100
  };
3973
- }, [interval2, tz, configReady, accountsKey]);
4101
+ }, [interval2, tz, configReady, showPicker, accountsKey]);
3974
4102
  useEffect3(() => {
3975
- if (!configReady) return;
4103
+ if (!configReady || showPicker) return;
3976
4104
  let active2 = true;
3977
4105
  let timer;
3978
4106
  const load = async () => {
@@ -3998,7 +4126,7 @@ function App({ interval: cliInterval }) {
3998
4126
  active2 = false;
3999
4127
  clearTimeout(timer);
4000
4128
  };
4001
- }, [billingMs, configReady, accountsKey]);
4129
+ }, [billingMs, configReady, showPicker, accountsKey]);
4002
4130
  const tableKey = `${effTableProvider}|${tableAccounts.map((a) => `${a.id}:${a.homeDir ?? ""}`).join(",")}|${tz}`;
4003
4131
  useEffect3(() => {
4004
4132
  setTable(null);
@@ -4636,7 +4764,8 @@ function App({ interval: cliInterval }) {
4636
4764
  }
4637
4765
  }
4638
4766
  )
4639
- ] })
4767
+ ] }),
4768
+ /* @__PURE__ */ jsx7(TotalsRow, { groups, stats, cols })
4640
4769
  ] }),
4641
4770
  tab === 1 && /* @__PURE__ */ jsxs7(Fragment4, { children: [
4642
4771
  tableProvs.length > 0 && /* @__PURE__ */ jsx7(TableProviderBar, { providers: tableProvs, active: effTableProvider, onSelect: (p) => {
@@ -4883,6 +5012,6 @@ setGlyphs(resolveGlyphs({
4883
5012
  isTTY: !!process.stdout.isTTY,
4884
5013
  platform: process.platform
4885
5014
  }));
4886
- var { waitUntilExit } = render(/* @__PURE__ */ jsx8(MouseProvider, { children: /* @__PURE__ */ jsx8(App, { interval }) }));
5015
+ var { waitUntilExit } = render(/* @__PURE__ */ jsx8(MouseProvider, { children: /* @__PURE__ */ jsx8(App, { interval, initialConfig: config }) }));
4887
5016
  await waitUntilExit();
4888
5017
  await flushDisk();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "Terminal dashboard for Claude Code, Codex, and Cursor usage, limits, and costs",
5
5
  "type": "module",
6
6
  "bin": {