tokmon 0.27.0 → 0.28.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 (25) hide show
  1. package/dist/{bootstrap-ink-3YNJFKCW.js → bootstrap-ink-5LQMSU4B.js} +27 -7
  2. package/dist/{chunk-RTGMDE7U.js → chunk-IWKSA64G.js} +139 -78
  3. package/dist/{chunk-OWPJUG4C.js → chunk-JK3U54G7.js} +2 -2
  4. package/dist/{chunk-VGYBQH5I.js → chunk-KCY53RUE.js} +1 -1
  5. package/dist/{chunk-4CY725NY.js → chunk-QOA6KOJQ.js} +1 -1
  6. package/dist/{cli-command-U7RZ2GTD.js → cli-command-HXKWB6IP.js} +2 -2
  7. package/dist/cli.js +4 -4
  8. package/dist/{daemon-PQWKWRC6.js → daemon-JCQF645I.js} +3 -3
  9. package/dist/{server-5WIWS27N.js → server-BJOXAGRI.js} +3 -3
  10. package/dist/web/assets/{Area-Tx1mUuqM.js → Area-DgGVYaNT.js} +1 -1
  11. package/dist/web/assets/{analytics-CAzklhXc.js → analytics-D5EvgF4d.js} +2 -2
  12. package/dist/web/assets/{breakdown-BoPnOv7Y.js → breakdown-CGdJkAHx.js} +1 -1
  13. package/dist/web/assets/{chart-jK7A9m7R.js → chart-D4KHj2-J.js} +1 -1
  14. package/dist/web/assets/{explore-e8wtwk7s.js → explore-B2QnGzfO.js} +1 -1
  15. package/dist/web/assets/{index-C8oe6EfM.js → index-C0txIEHK.js} +6 -6
  16. package/dist/web/assets/{models-_4IZNDxW.js → models-D7w7JJCX.js} +2 -2
  17. package/dist/web/assets/{overview-BBOfd_Fd.js → overview-BPz0j397.js} +2 -2
  18. package/dist/web/assets/{panel-CstB4hPm.js → panel-Da3rkvLu.js} +1 -1
  19. package/dist/web/assets/{primitives-G9ALG7ih.js → primitives-B8eGD8S_.js} +1 -1
  20. package/dist/web/assets/{settings-sheet-rq6vbYXM.js → settings-sheet-D6tnWqmt.js} +1 -1
  21. package/dist/web/assets/{share-sheet-BwmnebRB.js → share-sheet-BXrrMoLk.js} +1 -1
  22. package/dist/web/assets/{timeline-DkYWH6XW.js → timeline-CiGI4EmA.js} +1 -1
  23. package/dist/web/assets/{use-dialog-trap-Jci2xaMX.js → use-dialog-trap-Dhs8W7s1.js} +1 -1
  24. package/dist/web/index.html +1 -1
  25. package/package.json +1 -1
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createDaemonRpcClient
4
- } from "./chunk-4CY725NY.js";
4
+ } from "./chunk-QOA6KOJQ.js";
5
5
  import {
6
6
  accountsByProvider,
7
7
  buildAccounts,
8
8
  fetchPeak,
9
9
  modelColor
10
- } from "./chunk-VGYBQH5I.js";
10
+ } from "./chunk-KCY53RUE.js";
11
11
  import {
12
12
  PROVIDERS,
13
13
  PROVIDER_ORDER,
@@ -15,6 +15,7 @@ import {
15
15
  col,
16
16
  currency,
17
17
  detectProviders,
18
+ formatAgo,
18
19
  formatResetAt,
19
20
  resetAt,
20
21
  resolveTimezone,
@@ -23,7 +24,7 @@ import {
23
24
  time,
24
25
  tokens,
25
26
  withTimeout
26
- } from "./chunk-RTGMDE7U.js";
27
+ } from "./chunk-IWKSA64G.js";
27
28
  import {
28
29
  COLOR_PALETTE,
29
30
  DEFAULTS,
@@ -219,6 +220,12 @@ import { memo as memo2 } from "react";
219
220
  import { Box as Box2, Text as Text2 } from "ink";
220
221
 
221
222
  // src/ui/provider-card.logic.ts
223
+ var BILLING_STALE_AFTER_MS = 30 * 6e4;
224
+ function billingStaleLabel(billingUpdatedAt, now) {
225
+ if (billingUpdatedAt == null || billingUpdatedAt <= 0) return null;
226
+ if (now - billingUpdatedAt < BILLING_STALE_AFTER_MS) return null;
227
+ return `as of ${formatAgo(billingUpdatedAt, now)}`;
228
+ }
222
229
  function normalizePlan(plan) {
223
230
  if (plan == null) return null;
224
231
  const t = plan.trim();
@@ -253,7 +260,9 @@ function estimateCardHeights(groups, stats) {
253
260
  if (meta.hasUsage) h += 1;
254
261
  const multi = g.accounts.length > 1;
255
262
  g.accounts.forEach((a, i) => {
256
- const metricRows = stats.get(a.id)?.billing?.metrics.length || 1;
263
+ const s = stats.get(a.id);
264
+ const staleRow = s?.billing && !s.billing.error && s.billing.metrics.length > 0 && billingStaleLabel(s.billing.asOfMs ?? s.billingUpdatedAt, Date.now()) ? 1 : 0;
265
+ const metricRows = (s?.billing?.metrics.length || 1) + staleRow;
257
266
  h += metricRows + (multi ? 1 : 0) + (multi && i > 0 ? 1 : 0);
258
267
  });
259
268
  }
@@ -460,6 +469,7 @@ function LimitsBlock({ items, inner, showRowPlans, privacyMode, resetDisplay, tz
460
469
  const barW = Math.max(10, Math.min(46, inner - labelW - resetW - 5));
461
470
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map(({ account, s }, idx) => {
462
471
  const billing = s?.billing;
472
+ const staleLabel = billing && !billing.error && billing.metrics.length > 0 ? billingStaleLabel(billing.asOfMs ?? s?.billingUpdatedAt, Date.now()) : null;
463
473
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: showName && idx > 0 ? 1 : 0, children: [
464
474
  showName && (() => {
465
475
  const rowPlan = showRowPlans ? normalizePlan(billing?.plan) : null;
@@ -482,7 +492,16 @@ function LimitsBlock({ items, inner, showRowPlans, privacyMode, resetDisplay, tz
482
492
  !billing ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
483
493
  "Fetching",
484
494
  glyphs().ellipsis
485
- ] }) : billing.error ? /* @__PURE__ */ jsx2(Text2, { color: "red", wrap: "truncate-end", children: privacyMode ? redactEmail(billing.error) : billing.error }) : billing.metrics.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" }) : billing.metrics.map((m, i) => /* @__PURE__ */ jsx2(MetricRow, { m, color: account.color, barW, labelW, resetW, resetDisplay, tz }, `${m.label}${i}`))
495
+ ] }) : billing.error ? /* @__PURE__ */ jsx2(Text2, { color: "red", wrap: "truncate-end", children: privacyMode ? redactEmail(billing.error) : billing.error }) : billing.metrics.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" }) : billing.metrics.map((m, i) => /* @__PURE__ */ jsx2(MetricRow, { m, color: account.color, barW, labelW, resetW, resetDisplay, tz }, `${m.label}${i}`)),
496
+ staleLabel && /* @__PURE__ */ jsxs2(Text2, { color: "yellow", dimColor: true, children: [
497
+ glyphs().warn,
498
+ " ",
499
+ staleLabel,
500
+ " ",
501
+ glyphs().middot,
502
+ " refreshing",
503
+ glyphs().ellipsis
504
+ ] })
486
505
  ] }, account.id);
487
506
  }) });
488
507
  }
@@ -1711,7 +1730,8 @@ function toStatsMap(snapshot, accounts) {
1711
1730
  out.set(account.id, {
1712
1731
  account,
1713
1732
  dashboard: wa?.dashboard ?? null,
1714
- billing: wa?.billing ?? null
1733
+ billing: wa?.billing ?? null,
1734
+ billingUpdatedAt: wa?.billingUpdatedAt ?? null
1715
1735
  });
1716
1736
  }
1717
1737
  return out;
@@ -3441,7 +3461,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3441
3461
  if (webStartingRef.current) return;
3442
3462
  webStartingRef.current = true;
3443
3463
  try {
3444
- const { startWebServer } = await import("./server-5WIWS27N.js");
3464
+ const { startWebServer } = await import("./server-BJOXAGRI.js");
3445
3465
  const ctrl = await startWebServer({ config: cfg, log: false });
3446
3466
  webRef.current = ctrl;
3447
3467
  openUrl(ctrl.url);
@@ -892,7 +892,8 @@ var BillingResultSchema = Schema.Struct({
892
892
  name: Schema.String,
893
893
  usd: Schema.Finite,
894
894
  requests: Schema.Finite
895
- }))))
895
+ })))),
896
+ asOfMs: Schema.optionalKey(Schema.Finite)
896
897
  });
897
898
  var WebAccountSchema = Schema.Struct({
898
899
  id: Schema.String,
@@ -1003,6 +1004,16 @@ function formatTime(date, tz) {
1003
1004
  timeZone: tz
1004
1005
  });
1005
1006
  }
1007
+ function formatAgo(ms, now) {
1008
+ const seconds = Math.max(0, Math.round((now - ms) / 1e3));
1009
+ if (seconds < 2) return "just now";
1010
+ if (seconds < 60) return `${seconds}s ago`;
1011
+ const minutes = Math.round(seconds / 60);
1012
+ if (minutes < 60) return `${minutes}m ago`;
1013
+ const hours = Math.round(minutes / 60);
1014
+ if (hours < 24) return `${hours}h ago`;
1015
+ return `${Math.round(hours / 24)}d ago`;
1016
+ }
1006
1017
  function formatShortDate(label, opts = {}) {
1007
1018
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(label);
1008
1019
  if (!match) return label;
@@ -1128,7 +1139,7 @@ async function detectClaude(homeDir) {
1128
1139
  return false;
1129
1140
  }
1130
1141
  function claudePriceFor(model, timestamp = Date.now()) {
1131
- const m = model.toLowerCase().trim();
1142
+ const m = model.toLowerCase().trim().replace(/\[[^\]]*\]$/, "");
1132
1143
  for (const key of PRICE_KEYS) {
1133
1144
  if (!m.startsWith(key)) continue;
1134
1145
  const rest = m.slice(key.length);
@@ -2076,7 +2087,7 @@ async function fetchResetCredits(headers) {
2076
2087
  return void 0;
2077
2088
  }
2078
2089
  }
2079
- async function liveBilling(auth) {
2090
+ async function liveBilling(auth, failure) {
2080
2091
  try {
2081
2092
  const headers = {
2082
2093
  "Authorization": `Bearer ${auth.accessToken}`,
@@ -2085,7 +2096,10 @@ async function liveBilling(auth) {
2085
2096
  };
2086
2097
  if (auth.accountId) headers["ChatGPT-Account-Id"] = auth.accountId;
2087
2098
  const res = await fetch(USAGE_URL, { headers, signal: AbortSignal.timeout(1e4) });
2088
- if (!res.ok) return null;
2099
+ if (!res.ok) {
2100
+ if (failure) failure.status = res.status;
2101
+ return null;
2102
+ }
2089
2103
  const data = await readJson(res);
2090
2104
  if (!data) return null;
2091
2105
  const headerPct = (name) => {
@@ -2186,19 +2200,19 @@ async function snapshotBilling(homeDir, auth = null) {
2186
2200
  }
2187
2201
  async function codexBilling(account) {
2188
2202
  const auth = await getAuth2(account.homeDir);
2203
+ const failure = {};
2189
2204
  if (auth) {
2190
- const live = await liveBilling(auth);
2205
+ const live = await liveBilling(auth, failure);
2191
2206
  if (live) return live;
2192
2207
  }
2193
2208
  const snap = await snapshotBilling(account.homeDir, auth);
2194
2209
  if (snap && Date.now() - snap.asOfMs < SNAPSHOT_STALE_MS) {
2195
- const { asOfMs: _asOfMs, ...result } = snap;
2196
- return result;
2210
+ return snap;
2197
2211
  }
2198
2212
  return {
2199
2213
  plan: auth?.plan ?? snap?.plan ?? null,
2200
2214
  metrics: [],
2201
- error: auth ? "Usage API failed \u2014 run codex to refresh" : "Not logged in \u2014 run codex",
2215
+ error: auth ? `Usage API failed${failure.status ? ` (HTTP ${failure.status})` : ""} \u2014 run codex to refresh` : "Not logged in \u2014 run codex",
2202
2216
  ...identityFields(auth)
2203
2217
  };
2204
2218
  }
@@ -2789,8 +2803,15 @@ async function fetchLocalEntries(tz, homeDir) {
2789
2803
  function overlayEntries(api, local, tz) {
2790
2804
  if (api.length === 0) return local.map((l) => l.entry);
2791
2805
  if (local.length === 0) return api;
2792
- const apiDays = new Set(api.map((e) => dayKey(e.ts, tz)));
2793
- return [...api, ...local.filter((l) => !apiDays.has(l.day)).map((l) => l.entry)];
2806
+ let apiMin = Infinity;
2807
+ let apiMax = -Infinity;
2808
+ for (const e of api) {
2809
+ if (e.ts < apiMin) apiMin = e.ts;
2810
+ if (e.ts > apiMax) apiMax = e.ts;
2811
+ }
2812
+ const minDay = dayKey(apiMin, tz);
2813
+ const maxDay = dayKey(apiMax, tz);
2814
+ return [...api, ...local.filter((l) => l.day < minDay || l.day > maxDay).map((l) => l.entry)];
2794
2815
  }
2795
2816
  async function cursorEntries(since, tz, homeDir) {
2796
2817
  const [apiResult, local] = await Promise.all([fetchApiEntries(homeDir), fetchLocalEntries(tz, homeDir)]);
@@ -2833,6 +2854,33 @@ async function detectPi(homeDir) {
2833
2854
  return false;
2834
2855
  }
2835
2856
  }
2857
+ function recordToEntry(obj) {
2858
+ if (obj?.type !== "message") return null;
2859
+ const msg = obj.message;
2860
+ if (msg?.role !== "assistant" || !msg?.usage) return null;
2861
+ const u = msg.usage;
2862
+ const ts = timestampMs(obj.timestamp ?? msg.timestamp);
2863
+ if (ts === null) return null;
2864
+ const input = safeNum(u.input);
2865
+ const output = safeNum(u.output);
2866
+ const cacheRead = safeNum(u.cacheRead);
2867
+ const cacheCreate = safeNum(u.cacheWrite);
2868
+ if (input + output + cacheRead + cacheCreate === 0) return null;
2869
+ const c = u.cost ?? {};
2870
+ const costInput = finitePositive(c.input);
2871
+ const cacheSavings = input > 0 && cacheRead > 0 ? Math.max(0, cacheRead * (costInput / input) - finitePositive(c.cacheRead)) : 0;
2872
+ const model = typeof msg.responseModel === "string" && msg.responseModel || typeof msg.model === "string" && msg.model || "unknown";
2873
+ return {
2874
+ ts,
2875
+ model,
2876
+ cost: finitePositive(c.total),
2877
+ input,
2878
+ output,
2879
+ cacheCreate,
2880
+ cacheRead,
2881
+ cacheSavings
2882
+ };
2883
+ }
2836
2884
  async function parseFile3(path) {
2837
2885
  const entries = [];
2838
2886
  const stream = createReadStream4(path);
@@ -2844,32 +2892,8 @@ async function parseFile3(path) {
2844
2892
  if (!rawLine.includes('"usage"')) continue;
2845
2893
  try {
2846
2894
  const line = rawLine.charCodeAt(0) === 65279 ? rawLine.slice(1) : rawLine;
2847
- const obj = JSON.parse(line);
2848
- if (obj?.type !== "message") continue;
2849
- const msg = obj.message;
2850
- if (msg?.role !== "assistant" || !msg?.usage) continue;
2851
- const u = msg.usage;
2852
- const ts = timestampMs(obj.timestamp ?? msg.timestamp);
2853
- if (ts === null) continue;
2854
- const input = safeNum(u.input);
2855
- const output = safeNum(u.output);
2856
- const cacheRead = safeNum(u.cacheRead);
2857
- const cacheCreate = safeNum(u.cacheWrite);
2858
- if (input + output + cacheRead + cacheCreate === 0) continue;
2859
- const c = u.cost ?? {};
2860
- const costInput = finitePositive(c.input);
2861
- const cacheSavings = input > 0 && cacheRead > 0 ? Math.max(0, cacheRead * (costInput / input) - finitePositive(c.cacheRead)) : 0;
2862
- const model = typeof msg.responseModel === "string" && msg.responseModel || typeof msg.model === "string" && msg.model || "unknown";
2863
- entries.push({
2864
- ts,
2865
- model,
2866
- cost: finitePositive(c.total),
2867
- input,
2868
- output,
2869
- cacheCreate,
2870
- cacheRead,
2871
- cacheSavings
2872
- });
2895
+ const entry = recordToEntry(JSON.parse(line));
2896
+ if (entry) entries.push(entry);
2873
2897
  } catch {
2874
2898
  }
2875
2899
  }
@@ -2951,31 +2975,35 @@ async function findDb(homeDir) {
2951
2975
  async function detectOpencode(homeDir) {
2952
2976
  return await findDb(homeDir) !== null;
2953
2977
  }
2978
+ function rowToEntry(row) {
2979
+ const ts = finitePositive(row.ts);
2980
+ if (!ts) return null;
2981
+ const input = finitePositive(row.input);
2982
+ const output = finitePositive(row.output);
2983
+ const cacheRead = finitePositive(row.cacheRead);
2984
+ const cacheCreate = finitePositive(row.cacheWrite);
2985
+ if (input + output + cacheRead + cacheCreate === 0) return null;
2986
+ return {
2987
+ ts,
2988
+ model: typeof row.model === "string" && row.model ? row.model : "unknown",
2989
+ cost: finitePositive(row.cost),
2990
+ input,
2991
+ output,
2992
+ cacheCreate,
2993
+ cacheRead,
2994
+ cacheSavings: 0
2995
+ };
2996
+ }
2954
2997
  async function loadEntries4(since, homeDir) {
2955
2998
  const db = await findDb(homeDir);
2956
2999
  if (!db) return [];
2957
- const sql = "SELECT CASE WHEN time_created < 10000000000 THEN time_created * 1000 ELSE time_created END AS ts, json_extract(data,'$.modelID') AS model, json_extract(data,'$.cost') AS cost, json_extract(data,'$.tokens.input') AS input, json_extract(data,'$.tokens.output') AS output, json_extract(data,'$.tokens.reasoning') AS reasoning, json_extract(data,'$.tokens.cache.read') AS cacheRead, json_extract(data,'$.tokens.cache.write') AS cacheWrite FROM message WHERE json_valid(data) AND json_extract(data,'$.role')='assistant' AND json_type(data,'$.tokens')='object' AND (CASE WHEN time_created < 10000000000 THEN time_created * 1000 ELSE time_created END) >= ?;";
3000
+ const sql = "SELECT CASE WHEN time_created < 10000000000 THEN time_created * 1000 ELSE time_created END AS ts, json_extract(data,'$.modelID') AS model, json_extract(data,'$.cost') AS cost, json_extract(data,'$.tokens.input') AS input, json_extract(data,'$.tokens.output') AS output, json_extract(data,'$.tokens.cache.read') AS cacheRead, json_extract(data,'$.tokens.cache.write') AS cacheWrite FROM message WHERE json_valid(data) AND json_extract(data,'$.role')='assistant' AND json_type(data,'$.tokens')='object' AND (CASE WHEN time_created < 10000000000 THEN time_created * 1000 ELSE time_created END) >= ?;";
2958
3001
  const res = await runSqlite(db, sql, [Math.floor(since)]);
2959
3002
  if (res.status !== "ok") return [];
2960
3003
  const entries = [];
2961
3004
  for (const row of res.rows) {
2962
- const ts = finitePositive(row.ts);
2963
- if (!ts) continue;
2964
- const input = finitePositive(row.input);
2965
- const output = finitePositive(row.output) + finitePositive(row.reasoning);
2966
- const cacheRead = finitePositive(row.cacheRead);
2967
- const cacheCreate = finitePositive(row.cacheWrite);
2968
- if (input + output + cacheRead + cacheCreate === 0) continue;
2969
- entries.push({
2970
- ts,
2971
- model: typeof row.model === "string" && row.model ? row.model : "unknown",
2972
- cost: finitePositive(row.cost),
2973
- input,
2974
- output,
2975
- cacheCreate,
2976
- cacheRead,
2977
- cacheSavings: 0
2978
- });
3005
+ const entry = rowToEntry(row);
3006
+ if (entry) entries.push(entry);
2979
3007
  }
2980
3008
  return entries;
2981
3009
  }
@@ -3442,11 +3470,16 @@ async function discoverGoogleOAuthClient() {
3442
3470
  }
3443
3471
  return null;
3444
3472
  }
3473
+ var discoverClient = discoverGoogleOAuthClient;
3445
3474
  async function resolveGoogleClient() {
3446
3475
  const envId = process.env.TOKMON_GOOGLE_CLIENT_ID?.trim();
3447
3476
  const envSecret = process.env.TOKMON_GOOGLE_CLIENT_SECRET?.trim();
3448
3477
  if (envId && envSecret) return { clientId: envId, clientSecret: envSecret };
3449
- if (cachedClient === void 0) cachedClient = await discoverGoogleOAuthClient();
3478
+ if (cachedClient == null) {
3479
+ const client = await discoverClient();
3480
+ if (client) cachedClient = client;
3481
+ return client;
3482
+ }
3450
3483
  return cachedClient;
3451
3484
  }
3452
3485
  async function refreshAccessToken(refreshToken) {
@@ -3737,38 +3770,38 @@ import { createInterface as createInterface5 } from "readline";
3737
3770
  import { join as join16 } from "path";
3738
3771
  import { homedir as homedir12 } from "os";
3739
3772
  var PRICING3 = {
3740
- "gemini-3.1-pro-preview": { in: 2e-6, out: 12e-6, cr: 2e-7 },
3741
- "gemini-3.1-pro": { in: 2e-6, out: 12e-6, cr: 2e-7 },
3742
- "gemini-3-pro-preview": { in: 2e-6, out: 12e-6, cr: 2e-7 },
3743
- "gemini-3-pro": { in: 2e-6, out: 12e-6, cr: 2e-7 },
3773
+ "gemini-3.1-pro-preview": { in: 2e-6, out: 12e-6, cr: 2e-7, long: { in: 4e-6, out: 18e-6, cr: 4e-7 } },
3774
+ "gemini-3.1-pro": { in: 2e-6, out: 12e-6, cr: 2e-7, long: { in: 4e-6, out: 18e-6, cr: 4e-7 } },
3775
+ "gemini-3-pro-preview": { in: 2e-6, out: 12e-6, cr: 2e-7, long: { in: 4e-6, out: 18e-6, cr: 4e-7 } },
3776
+ "gemini-3-pro": { in: 2e-6, out: 12e-6, cr: 2e-7, long: { in: 4e-6, out: 18e-6, cr: 4e-7 } },
3744
3777
  "gemini-3.5-flash": { in: 75e-8, out: 45e-7, cr: 75e-9 },
3745
3778
  "gemini-3-flash-preview": { in: 5e-7, out: 3e-6, cr: 5e-8 },
3746
3779
  "gemini-3-flash": { in: 5e-7, out: 3e-6, cr: 5e-8 },
3747
3780
  "gemini-2.5-flash-lite": { in: 1e-7, out: 4e-7, cr: 1e-8 },
3748
3781
  "gemini-3.1-flash-lite": { in: 25e-8, out: 15e-7, cr: 25e-9 },
3749
3782
  "gemini-2.5-flash": { in: 3e-7, out: 25e-7, cr: 3e-8 },
3750
- "gemini-2.5-pro": { in: 125e-8, out: 1e-5, cr: 125e-9 },
3783
+ "gemini-2.5-pro": { in: 125e-8, out: 1e-5, cr: 125e-9, long: { in: 25e-7, out: 15e-6, cr: 25e-8 } },
3751
3784
  "gemini-2.0-flash": { in: 1e-7, out: 4e-7, cr: 25e-9 }
3752
3785
  };
3753
3786
  var PRICE_KEYS3 = Object.keys(PRICING3).sort((a, b) => b.length - a.length);
3754
- var ZERO_PRICE2 = { in: 0, out: 0, cr: 0 };
3755
- var GEMINI_31_PRO_LONG_PRICE = { in: 4e-6, out: 18e-6, cr: 4e-7 };
3787
+ var FALLBACK_PRICE2 = PRICING3["gemini-3.1-pro"];
3756
3788
  var MAX_SESSION_FILE_BYTES = 16 * 1024 * 1024;
3757
3789
  var MAX_JSON_ENTRIES = 1e5;
3758
3790
  function geminiTmpDir(homeDir) {
3759
3791
  return join16(homeDir ?? homedir12(), ".gemini", "tmp");
3760
3792
  }
3761
- function geminiPriceFor(model, promptTokens = 0) {
3762
- const m = model.toLowerCase().trim();
3793
+ function resolvePricing(m) {
3763
3794
  for (const key of PRICE_KEYS3) {
3764
3795
  if (!m.startsWith(key)) continue;
3765
3796
  const rest = m.slice(key.length);
3766
- if (rest === "" || rest[0] === "-") {
3767
- if (key.startsWith("gemini-3.1-pro") && promptTokens > 2e5) return GEMINI_31_PRO_LONG_PRICE;
3768
- return PRICING3[key];
3769
- }
3797
+ if (rest === "" || rest[0] === "-") return PRICING3[key];
3770
3798
  }
3771
- return ZERO_PRICE2;
3799
+ return FALLBACK_PRICE2;
3800
+ }
3801
+ function geminiPriceFor(model, promptTokens = 0) {
3802
+ const p = resolvePricing(model.toLowerCase().trim());
3803
+ if (promptTokens > 2e5 && p.long) return p.long;
3804
+ return { in: p.in, out: p.out, cr: p.cr };
3772
3805
  }
3773
3806
  function shortModel2(model) {
3774
3807
  return model.replace(/(-preview|-customtools)+$/, "");
@@ -4242,7 +4275,7 @@ var PRICING4 = {
4242
4275
  "grok-code-fast": { in: 1e-6, cr: 2e-7, out: 2e-6 },
4243
4276
  "grok-build": { in: 1e-6, cr: 2e-7, out: 2e-6 }
4244
4277
  };
4245
- var FALLBACK_PRICE2 = PRICING4["grok-4.5"];
4278
+ var FALLBACK_PRICE3 = PRICING4["grok-4.5"];
4246
4279
  var PRICE_KEYS4 = Object.keys(PRICING4).sort((a, b) => b.length - a.length);
4247
4280
  var MAX_SESSION_GROUPS = 128;
4248
4281
  var MAX_SESSIONS_PER_GROUP = 512;
@@ -4265,7 +4298,7 @@ function priceFor(model) {
4265
4298
  for (const key of PRICE_KEYS4) {
4266
4299
  if (modelKeyMatches2(m, key)) return PRICING4[key];
4267
4300
  }
4268
- return FALLBACK_PRICE2;
4301
+ return FALLBACK_PRICE3;
4269
4302
  }
4270
4303
  async function detectGrok(homeDir) {
4271
4304
  for (const home of grokHomes(homeDir)) {
@@ -4279,6 +4312,14 @@ async function detectGrok(homeDir) {
4279
4312
  }
4280
4313
  return false;
4281
4314
  }
4315
+ async function byMtimeDesc(dir, names) {
4316
+ const stated = await Promise.all(names.map(async (name) => {
4317
+ const st = await fsStat6(join19(dir, name)).catch(() => null);
4318
+ return { name, mtimeMs: st ? st.mtimeMs : -Infinity };
4319
+ }));
4320
+ stated.sort((a, b) => b.mtimeMs - a.mtimeMs || a.name.localeCompare(b.name));
4321
+ return stated.map((s) => s.name);
4322
+ }
4282
4323
  async function loadSessionModels(home) {
4283
4324
  const out = /* @__PURE__ */ new Map();
4284
4325
  const sessionsRoot = join19(home, "sessions");
@@ -4288,8 +4329,9 @@ async function loadSessionModels(home) {
4288
4329
  } catch {
4289
4330
  return out;
4290
4331
  }
4291
- for (const group of groups.sort().reverse().slice(0, MAX_SESSION_GROUPS)) {
4292
- if (group === "session_search.sqlite" || group.startsWith(".")) continue;
4332
+ const groupNames = groups.filter((g) => g !== "session_search.sqlite" && !g.startsWith("."));
4333
+ const orderedGroups = (await byMtimeDesc(sessionsRoot, groupNames)).slice(0, MAX_SESSION_GROUPS);
4334
+ for (const group of orderedGroups) {
4293
4335
  const groupDir = join19(sessionsRoot, group);
4294
4336
  let sessions;
4295
4337
  try {
@@ -4297,7 +4339,9 @@ async function loadSessionModels(home) {
4297
4339
  } catch {
4298
4340
  continue;
4299
4341
  }
4300
- for (const sid of sessions.sort().reverse().slice(0, MAX_SESSIONS_PER_GROUP)) {
4342
+ const sessionNames = sessions.filter((s) => !s.startsWith("."));
4343
+ const orderedSessions = (await byMtimeDesc(groupDir, sessionNames)).slice(0, MAX_SESSIONS_PER_GROUP);
4344
+ for (const sid of orderedSessions) {
4301
4345
  try {
4302
4346
  const raw = JSON.parse(await readFile8(join19(groupDir, sid, "summary.json"), "utf-8"));
4303
4347
  const model = typeof raw?.current_model_id === "string" && raw.current_model_id ? raw.current_model_id : null;
@@ -4332,21 +4376,37 @@ function costOf2(model, input, cacheRead, output) {
4332
4376
  const cacheSavings = cacheRead * Math.max(0, p.in - p.cr);
4333
4377
  return { cost, cacheSavings };
4334
4378
  }
4379
+ var DEFAULT_MODEL = "grok-4.5";
4380
+ function modelChangeFrom(obj) {
4381
+ if (obj?.msg !== "model changed" && obj?.msg !== "backend_search: model switch") return null;
4382
+ const ctx = obj.ctx;
4383
+ if (!ctx || typeof ctx !== "object") return null;
4384
+ const c = ctx;
4385
+ const model = typeof c.model === "string" ? c.model : typeof c.new_model === "string" ? c.new_model : null;
4386
+ return model && model.trim() ? model : null;
4387
+ }
4335
4388
  async function parseUnifiedLog(path, models) {
4336
4389
  const entries = [];
4390
+ const activeModel = /* @__PURE__ */ new Map();
4337
4391
  const input = createReadStream6(path);
4338
4392
  input.on("error", () => {
4339
4393
  });
4340
4394
  const rl = createInterface6({ input, crlfDelay: Infinity });
4341
4395
  try {
4342
4396
  for await (const line of rl) {
4343
- if (!line.includes("shell.turn.inference_done")) continue;
4397
+ const isTurn = line.includes("shell.turn.inference_done");
4398
+ if (!isTurn && !line.includes("model changed") && !line.includes("model switch")) continue;
4344
4399
  try {
4345
4400
  const obj = JSON.parse(line.charCodeAt(0) === 65279 ? line.slice(1) : line);
4401
+ const sid = typeof obj.sid === "string" ? obj.sid : "unknown";
4402
+ const switched = modelChangeFrom(obj);
4403
+ if (switched) {
4404
+ activeModel.set(sid, switched);
4405
+ continue;
4406
+ }
4346
4407
  if (obj?.msg !== "shell.turn.inference_done" || !obj.ctx) continue;
4347
4408
  const ts = Date.parse(String(obj.ts ?? ""));
4348
4409
  if (!Number.isFinite(ts)) continue;
4349
- const sid = typeof obj.sid === "string" ? obj.sid : "unknown";
4350
4410
  const ctx = obj.ctx;
4351
4411
  const prompt = safeNum(ctx.prompt_tokens);
4352
4412
  const cached = safeNum(ctx.cached_prompt_tokens);
@@ -4355,7 +4415,7 @@ async function parseUnifiedLog(path, models) {
4355
4415
  const cacheRead = cached;
4356
4416
  const output = completion;
4357
4417
  if (inputTokens + cacheRead + output <= 0) continue;
4358
- const model = models.get(sid) ?? "grok-4.5";
4418
+ const model = activeModel.get(sid) ?? models.get(sid) ?? DEFAULT_MODEL;
4359
4419
  const { cost, cacheSavings } = costOf2(model, inputTokens, cacheRead, output);
4360
4420
  const loop = safeNum(ctx.loop_index);
4361
4421
  entries.push({
@@ -4398,7 +4458,7 @@ async function loadEntries6(since, homeDir) {
4398
4458
  files,
4399
4459
  async (path) => parseUnifiedLog(path, modelByHome.get(path) ?? /* @__PURE__ */ new Map()),
4400
4460
  since,
4401
- { fingerprint: { parser: `grok-unified-v2.${grokModelMapFingerprint(modelByHome)}` } }
4461
+ { fingerprint: { parser: `grok-unified-v3.${grokModelMapFingerprint(modelByHome)}` } }
4402
4462
  );
4403
4463
  }
4404
4464
  async function grokDashboard(tz, homeDir) {
@@ -4579,6 +4639,7 @@ export {
4579
4639
  readClaudeIdentity,
4580
4640
  codexHomes,
4581
4641
  identityFromIdToken,
4642
+ formatAgo,
4582
4643
  formatResetAt,
4583
4644
  currency,
4584
4645
  tokens,
@@ -11,7 +11,7 @@ import {
11
11
  colorHex,
12
12
  fetchPeak,
13
13
  namedHex
14
- } from "./chunk-VGYBQH5I.js";
14
+ } from "./chunk-KCY53RUE.js";
15
15
  import {
16
16
  PROVIDERS,
17
17
  TOKMON_CAPABILITIES,
@@ -22,7 +22,7 @@ import {
22
22
  detectProviders,
23
23
  resolveTimezone,
24
24
  withTimeout
25
- } from "./chunk-RTGMDE7U.js";
25
+ } from "./chunk-IWKSA64G.js";
26
26
  import {
27
27
  cacheDir,
28
28
  expandHome,
@@ -5,7 +5,7 @@ import {
5
5
  identityFromIdToken,
6
6
  readClaudeIdentity,
7
7
  readJson
8
- } from "./chunk-RTGMDE7U.js";
8
+ } from "./chunk-IWKSA64G.js";
9
9
  import {
10
10
  expandHome,
11
11
  slugify
@@ -3,7 +3,7 @@ import {
3
3
  TOKMON_WS_METHODS,
4
4
  TOKMON_WS_PATH,
5
5
  TokmonRpcGroup
6
- } from "./chunk-RTGMDE7U.js";
6
+ } from "./chunk-IWKSA64G.js";
7
7
 
8
8
  // src/client/daemon-rpc-client.ts
9
9
  import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Stream } from "effect";
@@ -6,7 +6,7 @@ import "./chunk-QM5E5RJZ.js";
6
6
  import "./chunk-SMPY52EV.js";
7
7
  import {
8
8
  createDaemonRpcClient
9
- } from "./chunk-4CY725NY.js";
9
+ } from "./chunk-QOA6KOJQ.js";
10
10
  import {
11
11
  PROVIDERS,
12
12
  antigravityStateDb,
@@ -26,7 +26,7 @@ import {
26
26
  startOfMonth,
27
27
  startOfWeek,
28
28
  withTimeout
29
- } from "./chunk-RTGMDE7U.js";
29
+ } from "./chunk-IWKSA64G.js";
30
30
  import {
31
31
  PROVIDER_IDS,
32
32
  configLocation
package/dist/cli.js CHANGED
@@ -41,7 +41,7 @@ function validateServeArgs(serveArgs) {
41
41
  }
42
42
  async function main() {
43
43
  if (subcommand && ["usage", "models", "query", "providers", "snapshot", "config"].includes(subcommand)) {
44
- const { runQueryCommand } = await import("./cli-command-U7RZ2GTD.js");
44
+ const { runQueryCommand } = await import("./cli-command-HXKWB6IP.js");
45
45
  try {
46
46
  const output = await runQueryCommand(
47
47
  subcommand,
@@ -60,14 +60,14 @@ Run tokmon ${subcommand} --help for usage.
60
60
  return;
61
61
  }
62
62
  if (subcommand === "__daemon") {
63
- const { runDaemon } = await import("./daemon-PQWKWRC6.js");
63
+ const { runDaemon } = await import("./daemon-JCQF645I.js");
64
64
  await runDaemon(args.slice(1), { foreground: false });
65
65
  process.exitCode ??= 0;
66
66
  return;
67
67
  }
68
68
  if (subcommand === "serve" || subcommand === "web") {
69
69
  validateServeArgs(args.slice(1));
70
- const { runDaemon } = await import("./daemon-PQWKWRC6.js");
70
+ const { runDaemon } = await import("./daemon-JCQF645I.js");
71
71
  await runDaemon(args.slice(1), { foreground: true });
72
72
  process.exitCode ??= 0;
73
73
  return;
@@ -128,7 +128,7 @@ Run tokmon ${subcommand} --help for usage.
128
128
  }));
129
129
  const daemon = await attachOrSpawn();
130
130
  const mode = daemon.kind === "spawned" ? "connected" : "degraded";
131
- const { bootstrapInk } = await import("./bootstrap-ink-3YNJFKCW.js");
131
+ const { bootstrapInk } = await import("./bootstrap-ink-5LQMSU4B.js");
132
132
  await bootstrapInk({ interval, config, daemon, mode });
133
133
  }
134
134
  void main().catch((error) => {
@@ -11,14 +11,14 @@ import {
11
11
  } from "./chunk-QM5E5RJZ.js";
12
12
  import {
13
13
  startWebServer
14
- } from "./chunk-OWPJUG4C.js";
14
+ } from "./chunk-JK3U54G7.js";
15
15
  import {
16
16
  appVersion
17
17
  } from "./chunk-SMPY52EV.js";
18
- import "./chunk-VGYBQH5I.js";
18
+ import "./chunk-KCY53RUE.js";
19
19
  import {
20
20
  flushDisk
21
- } from "./chunk-RTGMDE7U.js";
21
+ } from "./chunk-IWKSA64G.js";
22
22
  import {
23
23
  loadConfig
24
24
  } from "./chunk-E2YXYU73.js";
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startWebServer
4
- } from "./chunk-OWPJUG4C.js";
4
+ } from "./chunk-JK3U54G7.js";
5
5
  import "./chunk-SMPY52EV.js";
6
- import "./chunk-VGYBQH5I.js";
7
- import "./chunk-RTGMDE7U.js";
6
+ import "./chunk-KCY53RUE.js";
7
+ import "./chunk-IWKSA64G.js";
8
8
  import "./chunk-E2YXYU73.js";
9
9
  export {
10
10
  startWebServer