tokmon 0.27.0 → 0.28.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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/{bootstrap-ink-3YNJFKCW.js → bootstrap-ink-L7QSJBMS.js} +142 -12
  3. package/dist/{chunk-4CY725NY.js → chunk-3RTWFGGD.js} +1 -1
  4. package/dist/{chunk-AMD4PXDG.js → chunk-4HU4EI5T.js} +1 -1
  5. package/dist/{chunk-RTGMDE7U.js → chunk-CVKCVHS7.js} +143 -81
  6. package/dist/{chunk-VGYBQH5I.js → chunk-FMP3P2WV.js} +2 -2
  7. package/dist/{chunk-E2YXYU73.js → chunk-HP5UZCXP.js} +29 -0
  8. package/dist/{chunk-OWPJUG4C.js → chunk-S33XIUAW.js} +11 -11
  9. package/dist/{chunk-QM5E5RJZ.js → chunk-XDA5RJST.js} +1 -1
  10. package/dist/{cli-command-U7RZ2GTD.js → cli-command-75LP4IDS.js} +5 -5
  11. package/dist/cli.js +6 -6
  12. package/dist/{config-2PXUENQL.js → config-HXFJTNLM.js} +5 -1
  13. package/dist/{daemon-PQWKWRC6.js → daemon-CL4FJW26.js} +5 -5
  14. package/dist/{daemon-handle-2GVZT55B.js → daemon-handle-QQLJE46Y.js} +3 -3
  15. package/dist/server-EUO7CWYH.js +11 -0
  16. package/dist/web/assets/{Area-Tx1mUuqM.js → Area-CMXvOw33.js} +1 -1
  17. package/dist/web/assets/{analytics-CAzklhXc.js → analytics-DqdZXOJL.js} +2 -2
  18. package/dist/web/assets/{breakdown-BoPnOv7Y.js → breakdown-kOSSJ3mI.js} +1 -1
  19. package/dist/web/assets/{chart-jK7A9m7R.js → chart-cZoLgpmG.js} +1 -1
  20. package/dist/web/assets/{explore-e8wtwk7s.js → explore-vcsM8K9I.js} +1 -1
  21. package/dist/web/assets/index-BErCXT7f.css +1 -0
  22. package/dist/web/assets/index-DH3dOnbg.js +83 -0
  23. package/dist/web/assets/{models-_4IZNDxW.js → models-B5ikm83_.js} +2 -2
  24. package/dist/web/assets/{overview-BBOfd_Fd.js → overview-DD1kP8CP.js} +2 -2
  25. package/dist/web/assets/{panel-CstB4hPm.js → panel-DTQBwExW.js} +1 -1
  26. package/dist/web/assets/{primitives-G9ALG7ih.js → primitives-C36qJlA2.js} +1 -1
  27. package/dist/web/assets/settings-sheet-_i2zTQjf.js +1 -0
  28. package/dist/web/assets/{share-sheet-BwmnebRB.js → share-sheet-BLHmrFbm.js} +2 -2
  29. package/dist/web/assets/{timeline-DkYWH6XW.js → timeline-B4uG-Emo.js} +1 -1
  30. package/dist/web/assets/{use-dialog-trap-Jci2xaMX.js → use-dialog-trap-bfaXEMz9.js} +1 -1
  31. package/dist/web/index.html +2 -2
  32. package/package.json +1 -1
  33. package/dist/server-5WIWS27N.js +0 -11
  34. package/dist/web/assets/index-C8oe6EfM.js +0 -83
  35. package/dist/web/assets/index-NnKaHxPO.css +0 -1
  36. package/dist/web/assets/settings-sheet-rq6vbYXM.js +0 -1
@@ -5,7 +5,7 @@ import {
5
5
  envDir,
6
6
  expandHome,
7
7
  isValidTimezone
8
- } from "./chunk-E2YXYU73.js";
8
+ } from "./chunk-HP5UZCXP.js";
9
9
 
10
10
  // src/async.ts
11
11
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -744,8 +744,8 @@ import { Schema } from "effect";
744
744
  import * as Rpc from "effect/unstable/rpc/Rpc";
745
745
  import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
746
746
  var TOKMON_WS_PATH = "/ws";
747
- var TOKMON_PROTOCOL_VERSION = 2;
748
- var TOKMON_CAPABILITIES = ["config-cas", "config-revision"];
747
+ var TOKMON_PROTOCOL_VERSION = 3;
748
+ var TOKMON_CAPABILITIES = ["config-cas", "config-revision", "allowed-hosts"];
749
749
  var TOKMON_WS_METHODS = {
750
750
  getConfig: "tokmon.getConfig",
751
751
  setConfig: "tokmon.setConfig",
@@ -787,6 +787,7 @@ var ConfigSchema = Schema.Struct({
787
787
  defaultFocus: Schema.Literals(["all", "last"]),
788
788
  ascii: Schema.Literals(["auto", "on", "off"]),
789
789
  allowNetworkAccess: Schema.Boolean,
790
+ allowedHosts: Schema.Array(Schema.String),
790
791
  resetDisplay: Schema.Literals(["relative", "absolute"]),
791
792
  knownProviders: Schema.Array(ProviderIdSchema)
792
793
  });
@@ -892,7 +893,8 @@ var BillingResultSchema = Schema.Struct({
892
893
  name: Schema.String,
893
894
  usd: Schema.Finite,
894
895
  requests: Schema.Finite
895
- }))))
896
+ })))),
897
+ asOfMs: Schema.optionalKey(Schema.Finite)
896
898
  });
897
899
  var WebAccountSchema = Schema.Struct({
898
900
  id: Schema.String,
@@ -1003,6 +1005,16 @@ function formatTime(date, tz) {
1003
1005
  timeZone: tz
1004
1006
  });
1005
1007
  }
1008
+ function formatAgo(ms, now) {
1009
+ const seconds = Math.max(0, Math.round((now - ms) / 1e3));
1010
+ if (seconds < 2) return "just now";
1011
+ if (seconds < 60) return `${seconds}s ago`;
1012
+ const minutes = Math.round(seconds / 60);
1013
+ if (minutes < 60) return `${minutes}m ago`;
1014
+ const hours = Math.round(minutes / 60);
1015
+ if (hours < 24) return `${hours}h ago`;
1016
+ return `${Math.round(hours / 24)}d ago`;
1017
+ }
1006
1018
  function formatShortDate(label, opts = {}) {
1007
1019
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(label);
1008
1020
  if (!match) return label;
@@ -1128,7 +1140,7 @@ async function detectClaude(homeDir) {
1128
1140
  return false;
1129
1141
  }
1130
1142
  function claudePriceFor(model, timestamp = Date.now()) {
1131
- const m = model.toLowerCase().trim();
1143
+ const m = model.toLowerCase().trim().replace(/\[[^\]]*\]$/, "");
1132
1144
  for (const key of PRICE_KEYS) {
1133
1145
  if (!m.startsWith(key)) continue;
1134
1146
  const rest = m.slice(key.length);
@@ -2076,7 +2088,7 @@ async function fetchResetCredits(headers) {
2076
2088
  return void 0;
2077
2089
  }
2078
2090
  }
2079
- async function liveBilling(auth) {
2091
+ async function liveBilling(auth, failure) {
2080
2092
  try {
2081
2093
  const headers = {
2082
2094
  "Authorization": `Bearer ${auth.accessToken}`,
@@ -2085,7 +2097,10 @@ async function liveBilling(auth) {
2085
2097
  };
2086
2098
  if (auth.accountId) headers["ChatGPT-Account-Id"] = auth.accountId;
2087
2099
  const res = await fetch(USAGE_URL, { headers, signal: AbortSignal.timeout(1e4) });
2088
- if (!res.ok) return null;
2100
+ if (!res.ok) {
2101
+ if (failure) failure.status = res.status;
2102
+ return null;
2103
+ }
2089
2104
  const data = await readJson(res);
2090
2105
  if (!data) return null;
2091
2106
  const headerPct = (name) => {
@@ -2186,19 +2201,19 @@ async function snapshotBilling(homeDir, auth = null) {
2186
2201
  }
2187
2202
  async function codexBilling(account) {
2188
2203
  const auth = await getAuth2(account.homeDir);
2204
+ const failure = {};
2189
2205
  if (auth) {
2190
- const live = await liveBilling(auth);
2206
+ const live = await liveBilling(auth, failure);
2191
2207
  if (live) return live;
2192
2208
  }
2193
2209
  const snap = await snapshotBilling(account.homeDir, auth);
2194
2210
  if (snap && Date.now() - snap.asOfMs < SNAPSHOT_STALE_MS) {
2195
- const { asOfMs: _asOfMs, ...result } = snap;
2196
- return result;
2211
+ return snap;
2197
2212
  }
2198
2213
  return {
2199
2214
  plan: auth?.plan ?? snap?.plan ?? null,
2200
2215
  metrics: [],
2201
- error: auth ? "Usage API failed \u2014 run codex to refresh" : "Not logged in \u2014 run codex",
2216
+ error: auth ? `Usage API failed${failure.status ? ` (HTTP ${failure.status})` : ""} \u2014 run codex to refresh` : "Not logged in \u2014 run codex",
2202
2217
  ...identityFields(auth)
2203
2218
  };
2204
2219
  }
@@ -2789,8 +2804,15 @@ async function fetchLocalEntries(tz, homeDir) {
2789
2804
  function overlayEntries(api, local, tz) {
2790
2805
  if (api.length === 0) return local.map((l) => l.entry);
2791
2806
  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)];
2807
+ let apiMin = Infinity;
2808
+ let apiMax = -Infinity;
2809
+ for (const e of api) {
2810
+ if (e.ts < apiMin) apiMin = e.ts;
2811
+ if (e.ts > apiMax) apiMax = e.ts;
2812
+ }
2813
+ const minDay = dayKey(apiMin, tz);
2814
+ const maxDay = dayKey(apiMax, tz);
2815
+ return [...api, ...local.filter((l) => l.day < minDay || l.day > maxDay).map((l) => l.entry)];
2794
2816
  }
2795
2817
  async function cursorEntries(since, tz, homeDir) {
2796
2818
  const [apiResult, local] = await Promise.all([fetchApiEntries(homeDir), fetchLocalEntries(tz, homeDir)]);
@@ -2833,6 +2855,33 @@ async function detectPi(homeDir) {
2833
2855
  return false;
2834
2856
  }
2835
2857
  }
2858
+ function recordToEntry(obj) {
2859
+ if (obj?.type !== "message") return null;
2860
+ const msg = obj.message;
2861
+ if (msg?.role !== "assistant" || !msg?.usage) return null;
2862
+ const u = msg.usage;
2863
+ const ts = timestampMs(obj.timestamp ?? msg.timestamp);
2864
+ if (ts === null) return null;
2865
+ const input = safeNum(u.input);
2866
+ const output = safeNum(u.output);
2867
+ const cacheRead = safeNum(u.cacheRead);
2868
+ const cacheCreate = safeNum(u.cacheWrite);
2869
+ if (input + output + cacheRead + cacheCreate === 0) return null;
2870
+ const c = u.cost ?? {};
2871
+ const costInput = finitePositive(c.input);
2872
+ const cacheSavings = input > 0 && cacheRead > 0 ? Math.max(0, cacheRead * (costInput / input) - finitePositive(c.cacheRead)) : 0;
2873
+ const model = typeof msg.responseModel === "string" && msg.responseModel || typeof msg.model === "string" && msg.model || "unknown";
2874
+ return {
2875
+ ts,
2876
+ model,
2877
+ cost: finitePositive(c.total),
2878
+ input,
2879
+ output,
2880
+ cacheCreate,
2881
+ cacheRead,
2882
+ cacheSavings
2883
+ };
2884
+ }
2836
2885
  async function parseFile3(path) {
2837
2886
  const entries = [];
2838
2887
  const stream = createReadStream4(path);
@@ -2844,32 +2893,8 @@ async function parseFile3(path) {
2844
2893
  if (!rawLine.includes('"usage"')) continue;
2845
2894
  try {
2846
2895
  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
- });
2896
+ const entry = recordToEntry(JSON.parse(line));
2897
+ if (entry) entries.push(entry);
2873
2898
  } catch {
2874
2899
  }
2875
2900
  }
@@ -2951,31 +2976,35 @@ async function findDb(homeDir) {
2951
2976
  async function detectOpencode(homeDir) {
2952
2977
  return await findDb(homeDir) !== null;
2953
2978
  }
2979
+ function rowToEntry(row) {
2980
+ const ts = finitePositive(row.ts);
2981
+ if (!ts) return null;
2982
+ const input = finitePositive(row.input);
2983
+ const output = finitePositive(row.output);
2984
+ const cacheRead = finitePositive(row.cacheRead);
2985
+ const cacheCreate = finitePositive(row.cacheWrite);
2986
+ if (input + output + cacheRead + cacheCreate === 0) return null;
2987
+ return {
2988
+ ts,
2989
+ model: typeof row.model === "string" && row.model ? row.model : "unknown",
2990
+ cost: finitePositive(row.cost),
2991
+ input,
2992
+ output,
2993
+ cacheCreate,
2994
+ cacheRead,
2995
+ cacheSavings: 0
2996
+ };
2997
+ }
2954
2998
  async function loadEntries4(since, homeDir) {
2955
2999
  const db = await findDb(homeDir);
2956
3000
  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) >= ?;";
3001
+ 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
3002
  const res = await runSqlite(db, sql, [Math.floor(since)]);
2959
3003
  if (res.status !== "ok") return [];
2960
3004
  const entries = [];
2961
3005
  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
- });
3006
+ const entry = rowToEntry(row);
3007
+ if (entry) entries.push(entry);
2979
3008
  }
2980
3009
  return entries;
2981
3010
  }
@@ -3442,11 +3471,16 @@ async function discoverGoogleOAuthClient() {
3442
3471
  }
3443
3472
  return null;
3444
3473
  }
3474
+ var discoverClient = discoverGoogleOAuthClient;
3445
3475
  async function resolveGoogleClient() {
3446
3476
  const envId = process.env.TOKMON_GOOGLE_CLIENT_ID?.trim();
3447
3477
  const envSecret = process.env.TOKMON_GOOGLE_CLIENT_SECRET?.trim();
3448
3478
  if (envId && envSecret) return { clientId: envId, clientSecret: envSecret };
3449
- if (cachedClient === void 0) cachedClient = await discoverGoogleOAuthClient();
3479
+ if (cachedClient == null) {
3480
+ const client = await discoverClient();
3481
+ if (client) cachedClient = client;
3482
+ return client;
3483
+ }
3450
3484
  return cachedClient;
3451
3485
  }
3452
3486
  async function refreshAccessToken(refreshToken) {
@@ -3737,38 +3771,38 @@ import { createInterface as createInterface5 } from "readline";
3737
3771
  import { join as join16 } from "path";
3738
3772
  import { homedir as homedir12 } from "os";
3739
3773
  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 },
3774
+ "gemini-3.1-pro-preview": { in: 2e-6, out: 12e-6, cr: 2e-7, long: { in: 4e-6, out: 18e-6, cr: 4e-7 } },
3775
+ "gemini-3.1-pro": { in: 2e-6, out: 12e-6, cr: 2e-7, long: { in: 4e-6, out: 18e-6, cr: 4e-7 } },
3776
+ "gemini-3-pro-preview": { in: 2e-6, out: 12e-6, cr: 2e-7, long: { in: 4e-6, out: 18e-6, cr: 4e-7 } },
3777
+ "gemini-3-pro": { in: 2e-6, out: 12e-6, cr: 2e-7, long: { in: 4e-6, out: 18e-6, cr: 4e-7 } },
3744
3778
  "gemini-3.5-flash": { in: 75e-8, out: 45e-7, cr: 75e-9 },
3745
3779
  "gemini-3-flash-preview": { in: 5e-7, out: 3e-6, cr: 5e-8 },
3746
3780
  "gemini-3-flash": { in: 5e-7, out: 3e-6, cr: 5e-8 },
3747
3781
  "gemini-2.5-flash-lite": { in: 1e-7, out: 4e-7, cr: 1e-8 },
3748
3782
  "gemini-3.1-flash-lite": { in: 25e-8, out: 15e-7, cr: 25e-9 },
3749
3783
  "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 },
3784
+ "gemini-2.5-pro": { in: 125e-8, out: 1e-5, cr: 125e-9, long: { in: 25e-7, out: 15e-6, cr: 25e-8 } },
3751
3785
  "gemini-2.0-flash": { in: 1e-7, out: 4e-7, cr: 25e-9 }
3752
3786
  };
3753
3787
  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 };
3788
+ var FALLBACK_PRICE2 = PRICING3["gemini-3.1-pro"];
3756
3789
  var MAX_SESSION_FILE_BYTES = 16 * 1024 * 1024;
3757
3790
  var MAX_JSON_ENTRIES = 1e5;
3758
3791
  function geminiTmpDir(homeDir) {
3759
3792
  return join16(homeDir ?? homedir12(), ".gemini", "tmp");
3760
3793
  }
3761
- function geminiPriceFor(model, promptTokens = 0) {
3762
- const m = model.toLowerCase().trim();
3794
+ function resolvePricing(m) {
3763
3795
  for (const key of PRICE_KEYS3) {
3764
3796
  if (!m.startsWith(key)) continue;
3765
3797
  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
- }
3798
+ if (rest === "" || rest[0] === "-") return PRICING3[key];
3770
3799
  }
3771
- return ZERO_PRICE2;
3800
+ return FALLBACK_PRICE2;
3801
+ }
3802
+ function geminiPriceFor(model, promptTokens = 0) {
3803
+ const p = resolvePricing(model.toLowerCase().trim());
3804
+ if (promptTokens > 2e5 && p.long) return p.long;
3805
+ return { in: p.in, out: p.out, cr: p.cr };
3772
3806
  }
3773
3807
  function shortModel2(model) {
3774
3808
  return model.replace(/(-preview|-customtools)+$/, "");
@@ -4242,7 +4276,7 @@ var PRICING4 = {
4242
4276
  "grok-code-fast": { in: 1e-6, cr: 2e-7, out: 2e-6 },
4243
4277
  "grok-build": { in: 1e-6, cr: 2e-7, out: 2e-6 }
4244
4278
  };
4245
- var FALLBACK_PRICE2 = PRICING4["grok-4.5"];
4279
+ var FALLBACK_PRICE3 = PRICING4["grok-4.5"];
4246
4280
  var PRICE_KEYS4 = Object.keys(PRICING4).sort((a, b) => b.length - a.length);
4247
4281
  var MAX_SESSION_GROUPS = 128;
4248
4282
  var MAX_SESSIONS_PER_GROUP = 512;
@@ -4265,7 +4299,7 @@ function priceFor(model) {
4265
4299
  for (const key of PRICE_KEYS4) {
4266
4300
  if (modelKeyMatches2(m, key)) return PRICING4[key];
4267
4301
  }
4268
- return FALLBACK_PRICE2;
4302
+ return FALLBACK_PRICE3;
4269
4303
  }
4270
4304
  async function detectGrok(homeDir) {
4271
4305
  for (const home of grokHomes(homeDir)) {
@@ -4279,6 +4313,14 @@ async function detectGrok(homeDir) {
4279
4313
  }
4280
4314
  return false;
4281
4315
  }
4316
+ async function byMtimeDesc(dir, names) {
4317
+ const stated = await Promise.all(names.map(async (name) => {
4318
+ const st = await fsStat6(join19(dir, name)).catch(() => null);
4319
+ return { name, mtimeMs: st ? st.mtimeMs : -Infinity };
4320
+ }));
4321
+ stated.sort((a, b) => b.mtimeMs - a.mtimeMs || a.name.localeCompare(b.name));
4322
+ return stated.map((s) => s.name);
4323
+ }
4282
4324
  async function loadSessionModels(home) {
4283
4325
  const out = /* @__PURE__ */ new Map();
4284
4326
  const sessionsRoot = join19(home, "sessions");
@@ -4288,8 +4330,9 @@ async function loadSessionModels(home) {
4288
4330
  } catch {
4289
4331
  return out;
4290
4332
  }
4291
- for (const group of groups.sort().reverse().slice(0, MAX_SESSION_GROUPS)) {
4292
- if (group === "session_search.sqlite" || group.startsWith(".")) continue;
4333
+ const groupNames = groups.filter((g) => g !== "session_search.sqlite" && !g.startsWith("."));
4334
+ const orderedGroups = (await byMtimeDesc(sessionsRoot, groupNames)).slice(0, MAX_SESSION_GROUPS);
4335
+ for (const group of orderedGroups) {
4293
4336
  const groupDir = join19(sessionsRoot, group);
4294
4337
  let sessions;
4295
4338
  try {
@@ -4297,7 +4340,9 @@ async function loadSessionModels(home) {
4297
4340
  } catch {
4298
4341
  continue;
4299
4342
  }
4300
- for (const sid of sessions.sort().reverse().slice(0, MAX_SESSIONS_PER_GROUP)) {
4343
+ const sessionNames = sessions.filter((s) => !s.startsWith("."));
4344
+ const orderedSessions = (await byMtimeDesc(groupDir, sessionNames)).slice(0, MAX_SESSIONS_PER_GROUP);
4345
+ for (const sid of orderedSessions) {
4301
4346
  try {
4302
4347
  const raw = JSON.parse(await readFile8(join19(groupDir, sid, "summary.json"), "utf-8"));
4303
4348
  const model = typeof raw?.current_model_id === "string" && raw.current_model_id ? raw.current_model_id : null;
@@ -4332,21 +4377,37 @@ function costOf2(model, input, cacheRead, output) {
4332
4377
  const cacheSavings = cacheRead * Math.max(0, p.in - p.cr);
4333
4378
  return { cost, cacheSavings };
4334
4379
  }
4380
+ var DEFAULT_MODEL = "grok-4.5";
4381
+ function modelChangeFrom(obj) {
4382
+ if (obj?.msg !== "model changed" && obj?.msg !== "backend_search: model switch") return null;
4383
+ const ctx = obj.ctx;
4384
+ if (!ctx || typeof ctx !== "object") return null;
4385
+ const c = ctx;
4386
+ const model = typeof c.model === "string" ? c.model : typeof c.new_model === "string" ? c.new_model : null;
4387
+ return model && model.trim() ? model : null;
4388
+ }
4335
4389
  async function parseUnifiedLog(path, models) {
4336
4390
  const entries = [];
4391
+ const activeModel = /* @__PURE__ */ new Map();
4337
4392
  const input = createReadStream6(path);
4338
4393
  input.on("error", () => {
4339
4394
  });
4340
4395
  const rl = createInterface6({ input, crlfDelay: Infinity });
4341
4396
  try {
4342
4397
  for await (const line of rl) {
4343
- if (!line.includes("shell.turn.inference_done")) continue;
4398
+ const isTurn = line.includes("shell.turn.inference_done");
4399
+ if (!isTurn && !line.includes("model changed") && !line.includes("model switch")) continue;
4344
4400
  try {
4345
4401
  const obj = JSON.parse(line.charCodeAt(0) === 65279 ? line.slice(1) : line);
4402
+ const sid = typeof obj.sid === "string" ? obj.sid : "unknown";
4403
+ const switched = modelChangeFrom(obj);
4404
+ if (switched) {
4405
+ activeModel.set(sid, switched);
4406
+ continue;
4407
+ }
4346
4408
  if (obj?.msg !== "shell.turn.inference_done" || !obj.ctx) continue;
4347
4409
  const ts = Date.parse(String(obj.ts ?? ""));
4348
4410
  if (!Number.isFinite(ts)) continue;
4349
- const sid = typeof obj.sid === "string" ? obj.sid : "unknown";
4350
4411
  const ctx = obj.ctx;
4351
4412
  const prompt = safeNum(ctx.prompt_tokens);
4352
4413
  const cached = safeNum(ctx.cached_prompt_tokens);
@@ -4355,7 +4416,7 @@ async function parseUnifiedLog(path, models) {
4355
4416
  const cacheRead = cached;
4356
4417
  const output = completion;
4357
4418
  if (inputTokens + cacheRead + output <= 0) continue;
4358
- const model = models.get(sid) ?? "grok-4.5";
4419
+ const model = activeModel.get(sid) ?? models.get(sid) ?? DEFAULT_MODEL;
4359
4420
  const { cost, cacheSavings } = costOf2(model, inputTokens, cacheRead, output);
4360
4421
  const loop = safeNum(ctx.loop_index);
4361
4422
  entries.push({
@@ -4398,7 +4459,7 @@ async function loadEntries6(since, homeDir) {
4398
4459
  files,
4399
4460
  async (path) => parseUnifiedLog(path, modelByHome.get(path) ?? /* @__PURE__ */ new Map()),
4400
4461
  since,
4401
- { fingerprint: { parser: `grok-unified-v2.${grokModelMapFingerprint(modelByHome)}` } }
4462
+ { fingerprint: { parser: `grok-unified-v3.${grokModelMapFingerprint(modelByHome)}` } }
4402
4463
  );
4403
4464
  }
4404
4465
  async function grokDashboard(tz, homeDir) {
@@ -4579,6 +4640,7 @@ export {
4579
4640
  readClaudeIdentity,
4580
4641
  codexHomes,
4581
4642
  identityFromIdToken,
4643
+ formatAgo,
4582
4644
  formatResetAt,
4583
4645
  currency,
4584
4646
  tokens,
@@ -5,11 +5,11 @@ import {
5
5
  identityFromIdToken,
6
6
  readClaudeIdentity,
7
7
  readJson
8
- } from "./chunk-RTGMDE7U.js";
8
+ } from "./chunk-CVKCVHS7.js";
9
9
  import {
10
10
  expandHome,
11
11
  slugify
12
- } from "./chunk-E2YXYU73.js";
12
+ } from "./chunk-HP5UZCXP.js";
13
13
 
14
14
  // src/accounts.ts
15
15
  import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
@@ -26,6 +26,7 @@ var DEFAULTS = {
26
26
  defaultFocus: "all",
27
27
  ascii: "auto",
28
28
  allowNetworkAccess: false,
29
+ allowedHosts: [],
29
30
  resetDisplay: "relative",
30
31
  knownProviders: []
31
32
  };
@@ -147,6 +148,27 @@ function sameJson(a, b) {
147
148
  return false;
148
149
  }
149
150
  }
151
+ function normalizeAllowedHost(value) {
152
+ if (typeof value !== "string") return null;
153
+ const candidate = value.trim();
154
+ if (!candidate) return null;
155
+ try {
156
+ const url = new URL(`http://${candidate}`);
157
+ if (url.username || url.password || url.port || url.pathname !== "/" || url.search || url.hash) return null;
158
+ const hostname = url.hostname.toLowerCase().replace(/\.$/, "");
159
+ if (!hostname || hostname.length > 253 || hostname.includes("*")) return null;
160
+ const labels = hostname.split(".");
161
+ if (labels.some((label) => !label || label.length > 63 || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(label))) return null;
162
+ return hostname;
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ function normalizeAllowedHosts(value) {
168
+ if (!Array.isArray(value)) return [];
169
+ const hosts = value.map(normalizeAllowedHost).filter((host) => host !== null);
170
+ return [...new Set(hosts)];
171
+ }
150
172
  function repairConfig(input) {
151
173
  const reasons = [];
152
174
  const parsed = isRecord(input) ? input : {};
@@ -192,6 +214,10 @@ function repairConfig(input) {
192
214
  if (parsed.knownProviders !== void 0 && !Array.isArray(parsed.knownProviders)) {
193
215
  reasons.push("knownProviders was not an array");
194
216
  }
217
+ const allowedHosts = normalizeAllowedHosts(parsed.allowedHosts);
218
+ if (parsed.allowedHosts !== void 0 && !sameJson(parsed.allowedHosts, allowedHosts)) {
219
+ reasons.push("allowedHosts contained invalid or duplicate hostnames");
220
+ }
195
221
  const activeAccountId = typeof parsed.activeAccountId === "string" && (accountIds.has(parsed.activeAccountId) || PROVIDER_IDS.includes(parsed.activeAccountId)) ? parsed.activeAccountId : null;
196
222
  if (parsed.activeAccountId !== void 0 && parsed.activeAccountId !== null && activeAccountId === null) {
197
223
  reasons.push("activeAccountId did not match a known account/provider");
@@ -217,6 +243,7 @@ function repairConfig(input) {
217
243
  defaultFocus: parsed.defaultFocus === "last" ? "last" : "all",
218
244
  ascii: parsed.ascii === "on" ? "on" : parsed.ascii === "off" ? "off" : "auto",
219
245
  allowNetworkAccess: parsed.allowNetworkAccess === true,
246
+ allowedHosts,
220
247
  resetDisplay: parsed.resetDisplay === "absolute" ? "absolute" : "relative",
221
248
  knownProviders
222
249
  };
@@ -389,6 +416,8 @@ export {
389
416
  getTrackedAccountRows,
390
417
  clampNum,
391
418
  isValidTimezone,
419
+ normalizeAllowedHost,
420
+ normalizeAllowedHosts,
392
421
  repairConfig,
393
422
  normalizeConfig,
394
423
  slugify,
@@ -11,7 +11,7 @@ import {
11
11
  colorHex,
12
12
  fetchPeak,
13
13
  namedHex
14
- } from "./chunk-VGYBQH5I.js";
14
+ } from "./chunk-FMP3P2WV.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-CVKCVHS7.js";
26
26
  import {
27
27
  cacheDir,
28
28
  expandHome,
@@ -30,7 +30,7 @@ import {
30
30
  normalizeConfig,
31
31
  saveConfig,
32
32
  snapshotCacheFile
33
- } from "./chunk-E2YXYU73.js";
33
+ } from "./chunk-HP5UZCXP.js";
34
34
 
35
35
  // src/web/server.ts
36
36
  import { createServer } from "http";
@@ -783,11 +783,11 @@ function isLoopbackHostHeader(value) {
783
783
  const hostname = hostnameFromHost(value);
784
784
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
785
785
  }
786
- function isAllowedHostHeader(value, allowNetworkAccess) {
786
+ function isAllowedHostHeader(value, allowNetworkAccess, allowedHosts = []) {
787
787
  if (isLoopbackHostHeader(value)) return true;
788
788
  if (!allowNetworkAccess) return false;
789
789
  const hostname = hostnameFromHost(value);
790
- return hostname !== null && isIP(hostname) !== 0;
790
+ return hostname !== null && (isIP(hostname) !== 0 || allowedHosts.includes(hostname));
791
791
  }
792
792
  function isSameOriginRequest(req) {
793
793
  const origin = header(req, "origin");
@@ -801,8 +801,8 @@ function isSameOriginRequest(req) {
801
801
  return false;
802
802
  }
803
803
  }
804
- function isAllowedLocalRequest(req, allowNetworkAccess) {
805
- return isAllowedHostHeader(header(req, "host"), allowNetworkAccess) && isSameOriginRequest(req);
804
+ function isAllowedLocalRequest(req, allowNetworkAccess, allowedHosts = []) {
805
+ return isAllowedHostHeader(header(req, "host"), allowNetworkAccess, allowedHosts) && isSameOriginRequest(req);
806
806
  }
807
807
 
808
808
  // src/web/ws.ts
@@ -890,7 +890,7 @@ async function mountWsRpc(server, deps) {
890
890
  )
891
891
  );
892
892
  const onUpgrade = (req, socket, head) => {
893
- if (!isAllowedLocalRequest(req, deps.state.config.allowNetworkAccess)) {
893
+ if (!isAllowedLocalRequest(req, deps.state.config.allowNetworkAccess, deps.state.config.allowedHosts)) {
894
894
  rejectUpgrade(socket);
895
895
  return;
896
896
  }
@@ -945,8 +945,8 @@ function tokenMatches(given, expected) {
945
945
  const token = Buffer.from(expected);
946
946
  return actual.length === token.length && timingSafeEqual(actual, token);
947
947
  }
948
- function guardHost(req, res, allowNetworkAccess) {
949
- if (isAllowedHostHeader(header2(req, "host"), allowNetworkAccess)) return true;
948
+ function guardHost(req, res, config) {
949
+ if (isAllowedHostHeader(header2(req, "host"), config.allowNetworkAccess, config.allowedHosts)) return true;
950
950
  sendJson(res, 403, { error: "forbidden" });
951
951
  return false;
952
952
  }
@@ -962,7 +962,7 @@ function createRouter(engine, state, vite, webRoot, wsToken, version) {
962
962
  const url = req.url || "/";
963
963
  const path = url.split("?")[0];
964
964
  const method = req.method || "GET";
965
- if (!guardHost(req, res, state.config.allowNetworkAccess)) return;
965
+ if (!guardHost(req, res, state.config)) return;
966
966
  if (path === "/api/data") {
967
967
  engine.touch();
968
968
  sendJson(res, 200, engine.snapshot() ?? { pending: true });
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  cacheDir
4
- } from "./chunk-E2YXYU73.js";
4
+ } from "./chunk-HP5UZCXP.js";
5
5
 
6
6
  // src/web/lockfile.ts
7
7
  import { closeSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  attachOrSpawn
4
- } from "./chunk-AMD4PXDG.js";
5
- import "./chunk-QM5E5RJZ.js";
4
+ } from "./chunk-4HU4EI5T.js";
5
+ import "./chunk-XDA5RJST.js";
6
6
  import "./chunk-SMPY52EV.js";
7
7
  import {
8
8
  createDaemonRpcClient
9
- } from "./chunk-4CY725NY.js";
9
+ } from "./chunk-3RTWFGGD.js";
10
10
  import {
11
11
  PROVIDERS,
12
12
  antigravityStateDb,
@@ -26,11 +26,11 @@ import {
26
26
  startOfMonth,
27
27
  startOfWeek,
28
28
  withTimeout
29
- } from "./chunk-RTGMDE7U.js";
29
+ } from "./chunk-CVKCVHS7.js";
30
30
  import {
31
31
  PROVIDER_IDS,
32
32
  configLocation
33
- } from "./chunk-E2YXYU73.js";
33
+ } from "./chunk-HP5UZCXP.js";
34
34
 
35
35
  // src/provider-locations.ts
36
36
  import { access } from "fs/promises";