codex-token-tracker 0.1.1 → 0.2.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.
package/dist/cli.js CHANGED
@@ -67,6 +67,11 @@ function normalizeModelName(model) {
67
67
  m = m.replace(/-preview$/, "");
68
68
  return m;
69
69
  }
70
+ function isOpenAIModel(model) {
71
+ const m = normalizeModelName(model);
72
+ if (m === "unknown") return true;
73
+ return /^(gpt[-.]|chatgpt|chat-latest|o[1-9](?:[-.]|$)|codex|text-|davinci|babbage|ada|curie)/.test(m);
74
+ }
70
75
  function tierOf(name) {
71
76
  for (const t2 of TIERS) if (name.split("-").includes(t2)) return t2;
72
77
  return null;
@@ -106,31 +111,65 @@ function resolvePrice(model, overrides) {
106
111
  }
107
112
  function computeCost(u, p) {
108
113
  const input = u.input ?? 0;
114
+ const rate = p.long && input > p.long.threshold ? p.long : p;
109
115
  const cached = Math.min(u.cached ?? 0, input);
110
116
  const cacheWrite = Math.min(u.cacheWrite ?? 0, Math.max(0, input - cached));
111
117
  const fresh = Math.max(0, input - cached - cacheWrite);
112
118
  const output = u.output ?? 0;
113
- const cost = fresh * p.input + cached * p.cachedInput + cacheWrite * (p.cacheWrite ?? p.input) + output * p.output;
119
+ const cost = fresh * rate.input + cached * rate.cachedInput + cacheWrite * (p.cacheWrite ?? rate.input) + output * rate.output;
114
120
  return cost / 1e6;
115
121
  }
116
- var DEFAULT_PRICING, FALLBACK_PRICE_KEY, TIERS;
122
+ var LONG_CONTEXT_THRESHOLD, long, DEFAULT_PRICING, FALLBACK_PRICE_KEY, TIERS;
117
123
  var init_pricing = __esm({
118
124
  "../shared/src/pricing.ts"() {
119
125
  "use strict";
126
+ LONG_CONTEXT_THRESHOLD = 272e3;
127
+ long = (threshold, input, cachedInput, output) => ({
128
+ threshold,
129
+ input,
130
+ cachedInput,
131
+ output
132
+ });
120
133
  DEFAULT_PRICING = {
121
- // GPT-5.x family
134
+ // GPT-5.6 family
135
+ "gpt-5.6-sol": { input: 4, cachedInput: 0.4, output: 20, long: long(LONG_CONTEXT_THRESHOLD, 8, 0.8, 30) },
136
+ "gpt-5.6-sol-codex": { input: 4, cachedInput: 0.4, output: 20, long: long(LONG_CONTEXT_THRESHOLD, 8, 0.8, 30) },
137
+ "gpt-5.6-terra": { input: 2, cachedInput: 0.2, output: 12, long: long(LONG_CONTEXT_THRESHOLD, 4, 0.4, 18) },
138
+ "gpt-5.6-terra-codex": { input: 2, cachedInput: 0.2, output: 12, long: long(LONG_CONTEXT_THRESHOLD, 4, 0.4, 18) },
139
+ "gpt-5.6-luna": { input: 0.2, cachedInput: 0.02, output: 1.2, long: long(LONG_CONTEXT_THRESHOLD, 0.4, 0.04, 1.8) },
140
+ "gpt-5.6-cyber": { input: 12.5, cachedInput: 1.25, output: 75 },
141
+ // GPT-5.5 family
142
+ "gpt-5.5": { input: 5, cachedInput: 0.5, output: 30, long: long(LONG_CONTEXT_THRESHOLD, 10, 1, 45) },
143
+ "gpt-5.5-codex": { input: 5, cachedInput: 0.5, output: 30, long: long(LONG_CONTEXT_THRESHOLD, 10, 1, 45) },
144
+ "gpt-5.5-pro": { input: 30, cachedInput: 30, output: 180, long: long(LONG_CONTEXT_THRESHOLD, 60, 60, 270) },
145
+ "gpt-5.5-cyber": { input: 12.5, cachedInput: 1.25, output: 75 },
146
+ // GPT-5.4 family
147
+ "gpt-5.4": { input: 2.5, cachedInput: 0.25, output: 15, long: long(LONG_CONTEXT_THRESHOLD, 5, 0.5, 22.5) },
148
+ "gpt-5.4-codex": { input: 2.5, cachedInput: 0.25, output: 15, long: long(LONG_CONTEXT_THRESHOLD, 5, 0.5, 22.5) },
149
+ "gpt-5.4-mini": { input: 0.75, cachedInput: 0.075, output: 4.5 },
150
+ "gpt-5.4-nano": { input: 0.2, cachedInput: 0.02, output: 1.25 },
151
+ "gpt-5.4-pro": { input: 30, cachedInput: 30, output: 180, long: long(LONG_CONTEXT_THRESHOLD, 60, 60, 270) },
152
+ // GPT-5.3 family (Codex-only release)
153
+ "gpt-5.3": { input: 1.75, cachedInput: 0.175, output: 14 },
154
+ "gpt-5.3-codex": { input: 1.75, cachedInput: 0.175, output: 14 },
155
+ // GPT-5.2 family
156
+ "gpt-5.2": { input: 1.75, cachedInput: 0.175, output: 14 },
157
+ "gpt-5.2-codex": { input: 1.75, cachedInput: 0.175, output: 14 },
158
+ "gpt-5.2-pro": { input: 21, cachedInput: 21, output: 168 },
159
+ // GPT-5.1 family
160
+ "gpt-5.1": { input: 1.25, cachedInput: 0.125, output: 10 },
161
+ "gpt-5.1-codex": { input: 1.25, cachedInput: 0.125, output: 10 },
162
+ "gpt-5.1-codex-max": { input: 1.25, cachedInput: 0.125, output: 10 },
163
+ "gpt-5.1-codex-mini": { input: 0.25, cachedInput: 0.025, output: 2 },
164
+ // GPT-5 family
122
165
  "gpt-5": { input: 1.25, cachedInput: 0.125, output: 10 },
123
166
  "gpt-5-codex": { input: 1.25, cachedInput: 0.125, output: 10 },
124
167
  "gpt-5-mini": { input: 0.25, cachedInput: 0.025, output: 2 },
125
168
  "gpt-5-nano": { input: 0.05, cachedInput: 5e-3, output: 0.4 },
126
169
  "gpt-5-pro": { input: 15, cachedInput: 15, output: 120 },
127
- "gpt-5.1": { input: 1.25, cachedInput: 0.125, output: 10 },
128
- "gpt-5.1-codex": { input: 1.25, cachedInput: 0.125, output: 10 },
129
- "gpt-5.1-codex-max": { input: 1.25, cachedInput: 0.125, output: 10 },
130
- "gpt-5.1-codex-mini": { input: 0.25, cachedInput: 0.025, output: 2 },
131
- "gpt-5.2": { input: 1.75, cachedInput: 0.175, output: 14 },
132
- "gpt-5.2-codex": { input: 1.75, cachedInput: 0.175, output: 14 },
133
- "gpt-5.2-pro": { input: 21, cachedInput: 21, output: 168 },
170
+ "gpt-5-search-api": { input: 1.25, cachedInput: 0.125, output: 10 },
171
+ // ChatGPT-tuned endpoint
172
+ "chat-latest": { input: 5, cachedInput: 0.5, output: 30 },
134
173
  // GPT-4.1 / 4o family
135
174
  "gpt-4.1": { input: 2, cachedInput: 0.5, output: 8 },
136
175
  "gpt-4.1-mini": { input: 0.4, cachedInput: 0.1, output: 1.6 },
@@ -146,7 +185,7 @@ var init_pricing = __esm({
146
185
  "o4-mini": { input: 1.1, cachedInput: 0.275, output: 4.4 },
147
186
  "codex-mini-latest": { input: 1.5, cachedInput: 0.375, output: 6 }
148
187
  };
149
- FALLBACK_PRICE_KEY = "gpt-5.2";
188
+ FALLBACK_PRICE_KEY = "gpt-5.3-codex";
150
189
  TIERS = ["mini", "nano", "pro"];
151
190
  }
152
191
  });
@@ -339,14 +378,14 @@ function createSessionParser(fallbackSessionId) {
339
378
  if (ts2 > lastActivityAt) lastActivityAt = ts2;
340
379
  }
341
380
  function onTokenCount(ts2, payload) {
342
- const info = payload.info;
381
+ const info2 = payload.info;
343
382
  let cumulative = null;
344
383
  let last = null;
345
- if (info && typeof info === "object") {
346
- cumulative = toUsage(info.total_token_usage);
347
- last = toUsage(info.last_token_usage);
348
- if (typeof info.model_context_window === "number") contextWindow = info.model_context_window;
349
- } else if (info === void 0) {
384
+ if (info2 && typeof info2 === "object") {
385
+ cumulative = toUsage(info2.total_token_usage);
386
+ last = toUsage(info2.last_token_usage);
387
+ if (typeof info2.model_context_window === "number") contextWindow = info2.model_context_window;
388
+ } else if (info2 === void 0) {
350
389
  cumulative = toUsage(payload);
351
390
  }
352
391
  const rl = payload.rate_limits;
@@ -1154,6 +1193,7 @@ function loadConfig() {
1154
1193
  cfg.sources = normalizeSources(stored.sources);
1155
1194
  cfg.trackAllProviders = stored.trackAllProviders === true;
1156
1195
  cfg.liveRateLimits = stored.liveRateLimits !== false;
1196
+ cfg.checkUpdates = stored.checkUpdates !== false;
1157
1197
  if (!(cfg.usageRefreshSec >= 15)) cfg.usageRefreshSec = DEFAULT_CONFIG.usageRefreshSec;
1158
1198
  if (!["auto", "en", "zh"].includes(cfg.language)) cfg.language = "auto";
1159
1199
  if (!(cfg.uploadIntervalSec >= 10)) cfg.uploadIntervalSec = DEFAULT_CONFIG.uploadIntervalSec;
@@ -1183,6 +1223,7 @@ function coerceConfigValue(key, raw) {
1183
1223
  case "launchAtLogin":
1184
1224
  case "trackAllProviders":
1185
1225
  case "liveRateLimits":
1226
+ case "checkUpdates":
1186
1227
  return parseBool(raw);
1187
1228
  case "extraSessionDirs": {
1188
1229
  const trimmed = raw.trim();
@@ -1285,7 +1326,8 @@ var init_config = __esm({
1285
1326
  sources: { ...DEFAULT_SOURCES },
1286
1327
  trackAllProviders: false,
1287
1328
  liveRateLimits: true,
1288
- usageRefreshSec: 60
1329
+ usageRefreshSec: 60,
1330
+ checkUpdates: true
1289
1331
  };
1290
1332
  EDITABLE_KEYS = [
1291
1333
  "dashboardUrl",
@@ -1298,7 +1340,8 @@ var init_config = __esm({
1298
1340
  "sources",
1299
1341
  "trackAllProviders",
1300
1342
  "liveRateLimits",
1301
- "usageRefreshSec"
1343
+ "usageRefreshSec",
1344
+ "checkUpdates"
1302
1345
  ];
1303
1346
  AGENT_NAME_RE = /^[a-z0-9][a-z0-9_.-]{0,31}$/;
1304
1347
  }
@@ -1380,8 +1423,20 @@ var init_en = __esm({
1380
1423
  never: "never",
1381
1424
  local: "Local",
1382
1425
  remote: "Other devices",
1426
+ // updates
1427
+ update: "Update",
1428
+ updateAvailable: "Update to v{version}",
1429
+ updateNewVersion: "Version {version} is available (you have v{current})",
1430
+ updateUpToDate: "Up to date",
1431
+ updateChecking: "Checking\u2026",
1432
+ updateInstalling: "Installing\u2026",
1433
+ updateInstalled: "Updated to v{version} \u2014 restart to apply",
1434
+ updateFailed: "Update failed \u2014 run this yourself:",
1435
+ updateCheckFailed: "Update check failed: {message}",
1436
+ checkForUpdates: "Check for updates",
1437
+ releaseNotes: "Release notes",
1383
1438
  // CLI
1384
- cliUsage: `Usage: codex-tracker [command] [options]
1439
+ cliUsage: `Usage: codex-token-tracker [command] [options] (alias: codex-tracker)
1385
1440
 
1386
1441
  Commands:
1387
1442
  (none) Start the menu bar app (falls back to agent mode without a display)
@@ -1392,10 +1447,12 @@ Commands:
1392
1447
  logout Disconnect this device
1393
1448
  status Print today's usage, live session and rate limits
1394
1449
  paths Show detected session directories (Codex, pi, OpenCode, Cline/Roo/Kilo, Hermes, custom)
1450
+ update Install the newest published version (--check only reports)
1395
1451
  config get|set Read or change settings (config set uploadIntervalSec 30, config set sources.pi false)
1396
1452
  lang <en|zh|auto> Set the display language
1397
1453
 
1398
1454
  Options:
1455
+ --check update: report the newest version without installing it
1399
1456
  --dashboard <url> Dashboard URL (self-hosted)
1400
1457
  --background Start the menu bar app detached and return
1401
1458
  --version, -v Print version
@@ -1437,7 +1494,13 @@ Options:
1437
1494
  cliConfigSet: "Set {key} = {value}",
1438
1495
  cliConfigUnknownKey: "Unknown config key: {key}. Keys: {keys}",
1439
1496
  cliLangSet: "Language set to {lang}",
1440
- cliConfigDir: "Config: {dir}"
1497
+ cliConfigDir: "Config: {dir}",
1498
+ cliUpdateLatest: "codex-token-tracker {version} is the latest version.",
1499
+ cliUpdateAvailable: "Update available: {current} \u2192 {latest}",
1500
+ cliUpdateRunning: "Running: {command}",
1501
+ cliUpdateDone: "Updated to {version}. Restart the menu bar app (or rerun the CLI) to use it.",
1502
+ cliUpdateFailed: "Update failed (exit {code}). Run it yourself: {command}",
1503
+ cliUpdateCheckFailed: "Could not reach the npm registry: {message}"
1441
1504
  };
1442
1505
  }
1443
1506
  });
@@ -1517,7 +1580,18 @@ var init_zh = __esm({
1517
1580
  never: "\u4ECE\u672A",
1518
1581
  local: "\u672C\u673A",
1519
1582
  remote: "\u5176\u4ED6\u8BBE\u5907",
1520
- cliUsage: `\u7528\u6CD5\uFF1Acodex-tracker [\u547D\u4EE4] [\u9009\u9879]
1583
+ update: "\u66F4\u65B0",
1584
+ updateAvailable: "\u66F4\u65B0\u5230 v{version}",
1585
+ updateNewVersion: "\u6709\u65B0\u7248\u672C {version}\uFF08\u5F53\u524D v{current}\uFF09",
1586
+ updateUpToDate: "\u5DF2\u662F\u6700\u65B0\u7248\u672C",
1587
+ updateChecking: "\u68C0\u67E5\u4E2D\u2026",
1588
+ updateInstalling: "\u5B89\u88C5\u4E2D\u2026",
1589
+ updateInstalled: "\u5DF2\u66F4\u65B0\u5230 v{version} \u2014 \u91CD\u542F\u540E\u751F\u6548",
1590
+ updateFailed: "\u66F4\u65B0\u5931\u8D25 \u2014 \u8BF7\u624B\u52A8\u6267\u884C\uFF1A",
1591
+ updateCheckFailed: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF1A{message}",
1592
+ checkForUpdates: "\u68C0\u67E5\u66F4\u65B0",
1593
+ releaseNotes: "\u66F4\u65B0\u65E5\u5FD7",
1594
+ cliUsage: `\u7528\u6CD5\uFF1Acodex-token-tracker [\u547D\u4EE4] [\u9009\u9879] \uFF08\u522B\u540D\uFF1Acodex-tracker\uFF09
1521
1595
 
1522
1596
  \u547D\u4EE4\uFF1A
1523
1597
  (\u65E0) \u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\uFF08\u65E0\u663E\u793A\u73AF\u5883\u65F6\u81EA\u52A8\u5207\u6362\u4E3A agent \u6A21\u5F0F\uFF09
@@ -1528,10 +1602,12 @@ var init_zh = __esm({
1528
1602
  logout \u65AD\u5F00\u672C\u8BBE\u5907
1529
1603
  status \u6253\u5370\u4ECA\u65E5\u7528\u91CF\u3001\u5B9E\u65F6\u4F1A\u8BDD\u4E0E\u9650\u989D
1530
1604
  paths \u663E\u793A\u68C0\u6D4B\u5230\u7684\u4F1A\u8BDD\u76EE\u5F55\uFF08Codex\u3001pi\u3001OpenCode\u3001Cline/Roo/Kilo\u3001Hermes\u3001\u81EA\u5B9A\u4E49\uFF09
1605
+ update \u5B89\u88C5\u6700\u65B0\u53D1\u5E03\u7248\u672C\uFF08--check \u53EA\u68C0\u67E5\u4E0D\u5B89\u88C5\uFF09
1531
1606
  config get|set \u8BFB\u53D6\u6216\u4FEE\u6539\u8BBE\u7F6E\uFF08config set uploadIntervalSec 30\uFF0Cconfig set sources.pi false\uFF09
1532
1607
  lang <en|zh|auto> \u8BBE\u7F6E\u663E\u793A\u8BED\u8A00
1533
1608
 
1534
1609
  \u9009\u9879\uFF1A
1610
+ --check update\uFF1A\u53EA\u62A5\u544A\u6700\u65B0\u7248\u672C\uFF0C\u4E0D\u6267\u884C\u5B89\u88C5
1535
1611
  --dashboard <url> \u4EEA\u8868\u76D8\u5730\u5740\uFF08\u81EA\u6258\u7BA1\uFF09
1536
1612
  --background \u4EE5\u540E\u53F0\u65B9\u5F0F\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u5E76\u7ACB\u5373\u8FD4\u56DE
1537
1613
  --version, -v \u663E\u793A\u7248\u672C
@@ -1573,7 +1649,13 @@ var init_zh = __esm({
1573
1649
  cliConfigSet: "\u5DF2\u8BBE\u7F6E {key} = {value}",
1574
1650
  cliConfigUnknownKey: "\u672A\u77E5\u914D\u7F6E\u9879\uFF1A{key}\u3002\u53EF\u7528\uFF1A{keys}",
1575
1651
  cliLangSet: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A {lang}",
1576
- cliConfigDir: "\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}"
1652
+ cliConfigDir: "\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}",
1653
+ cliUpdateLatest: "codex-token-tracker {version} \u5DF2\u662F\u6700\u65B0\u7248\u672C\u3002",
1654
+ cliUpdateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C\uFF1A{current} \u2192 {latest}",
1655
+ cliUpdateRunning: "\u6B63\u5728\u6267\u884C\uFF1A{command}",
1656
+ cliUpdateDone: "\u5DF2\u66F4\u65B0\u5230 {version}\u3002\u8BF7\u91CD\u542F\u83DC\u5355\u680F\u5E94\u7528\uFF08\u6216\u91CD\u65B0\u8FD0\u884C CLI\uFF09\u4EE5\u751F\u6548\u3002",
1657
+ cliUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF1A{command}",
1658
+ cliUpdateCheckFailed: "\u65E0\u6CD5\u8BBF\u95EE npm registry\uFF1A{message}"
1577
1659
  };
1578
1660
  }
1579
1661
  });
@@ -3194,12 +3276,12 @@ function createApi(pathParts = []) {
3194
3276
  `API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
3195
3277
  );
3196
3278
  }
3197
- const path12 = pathParts.slice(0, -1).join("/");
3279
+ const path13 = pathParts.slice(0, -1).join("/");
3198
3280
  const exportName = pathParts[pathParts.length - 1];
3199
3281
  if (exportName === "default") {
3200
- return path12;
3282
+ return path13;
3201
3283
  } else {
3202
- return path12 + ":" + exportName;
3284
+ return path13 + ":" + exportName;
3203
3285
  }
3204
3286
  } else if (prop === Symbol.toStringTag) {
3205
3287
  return "FunctionReference";
@@ -3494,8 +3576,8 @@ var init_simple_client_node = __esm({
3494
3576
  });
3495
3577
  require_node_gyp_build = __commonJS({
3496
3578
  "../node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports2, module2) {
3497
- var fs10 = __require("fs");
3498
- var path12 = __require("path");
3579
+ var fs11 = __require("fs");
3580
+ var path13 = __require("path");
3499
3581
  var os8 = __require("os");
3500
3582
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
3501
3583
  var vars2 = process.config && process.config.variables || {};
@@ -3512,21 +3594,21 @@ var init_simple_client_node = __esm({
3512
3594
  return runtimeRequire(load.resolve(dir));
3513
3595
  }
3514
3596
  load.resolve = load.path = function(dir) {
3515
- dir = path12.resolve(dir || ".");
3597
+ dir = path13.resolve(dir || ".");
3516
3598
  try {
3517
- var name = runtimeRequire(path12.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
3599
+ var name = runtimeRequire(path13.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
3518
3600
  if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
3519
3601
  } catch (err) {
3520
3602
  }
3521
3603
  if (!prebuildsOnly) {
3522
- var release = getFirst(path12.join(dir, "build/Release"), matchBuild);
3604
+ var release = getFirst(path13.join(dir, "build/Release"), matchBuild);
3523
3605
  if (release) return release;
3524
- var debug = getFirst(path12.join(dir, "build/Debug"), matchBuild);
3606
+ var debug = getFirst(path13.join(dir, "build/Debug"), matchBuild);
3525
3607
  if (debug) return debug;
3526
3608
  }
3527
3609
  var prebuild = resolve(dir);
3528
3610
  if (prebuild) return prebuild;
3529
- var nearby = resolve(path12.dirname(process.execPath));
3611
+ var nearby = resolve(path13.dirname(process.execPath));
3530
3612
  if (nearby) return nearby;
3531
3613
  var target = [
3532
3614
  "platform=" + platform,
@@ -3543,26 +3625,26 @@ var init_simple_client_node = __esm({
3543
3625
  ].filter(Boolean).join(" ");
3544
3626
  throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
3545
3627
  function resolve(dir2) {
3546
- var tuples = readdirSync(path12.join(dir2, "prebuilds")).map(parseTuple);
3628
+ var tuples = readdirSync(path13.join(dir2, "prebuilds")).map(parseTuple);
3547
3629
  var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
3548
3630
  if (!tuple) return;
3549
- var prebuilds = path12.join(dir2, "prebuilds", tuple.name);
3631
+ var prebuilds = path13.join(dir2, "prebuilds", tuple.name);
3550
3632
  var parsed = readdirSync(prebuilds).map(parseTags);
3551
3633
  var candidates = parsed.filter(matchTags(runtime, abi));
3552
3634
  var winner = candidates.sort(compareTags(runtime))[0];
3553
- if (winner) return path12.join(prebuilds, winner.file);
3635
+ if (winner) return path13.join(prebuilds, winner.file);
3554
3636
  }
3555
3637
  };
3556
3638
  function readdirSync(dir) {
3557
3639
  try {
3558
- return fs10.readdirSync(dir);
3640
+ return fs11.readdirSync(dir);
3559
3641
  } catch (err) {
3560
3642
  return [];
3561
3643
  }
3562
3644
  }
3563
3645
  function getFirst(dir, filter) {
3564
3646
  var files = readdirSync(dir).filter(filter);
3565
- return files[0] && path12.join(dir, files[0]);
3647
+ return files[0] && path13.join(dir, files[0]);
3566
3648
  }
3567
3649
  function matchBuild(name) {
3568
3650
  return /\.node$/.test(name);
@@ -3649,7 +3731,7 @@ var init_simple_client_node = __esm({
3649
3731
  return typeof window !== "undefined" && window.process && window.process.type === "renderer";
3650
3732
  }
3651
3733
  function isAlpine(platform2) {
3652
- return platform2 === "linux" && fs10.existsSync("/etc/alpine-release");
3734
+ return platform2 === "linux" && fs11.existsSync("/etc/alpine-release");
3653
3735
  }
3654
3736
  load.parseTags = parseTags;
3655
3737
  load.matchTags = matchTags;
@@ -7195,13 +7277,13 @@ var init_simple_client_node = __esm({
7195
7277
  }
7196
7278
  }
7197
7279
  if (this.options.verifyClient) {
7198
- const info = {
7280
+ const info2 = {
7199
7281
  origin: req.headers[`${version2 === 8 ? "sec-websocket-origin" : "origin"}`],
7200
7282
  secure: !!(req.socket.authorized || req.socket.encrypted),
7201
7283
  req
7202
7284
  };
7203
7285
  if (this.options.verifyClient.length === 2) {
7204
- this.options.verifyClient(info, (verified, code2, message, headers) => {
7286
+ this.options.verifyClient(info2, (verified, code2, message, headers) => {
7205
7287
  if (!verified) {
7206
7288
  return abortHandshake(socket, code2 || 401, message, headers);
7207
7289
  }
@@ -7217,7 +7299,7 @@ var init_simple_client_node = __esm({
7217
7299
  });
7218
7300
  return;
7219
7301
  }
7220
- if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
7302
+ if (!this.options.verifyClient(info2)) return abortHandshake(socket, 401);
7221
7303
  }
7222
7304
  this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
7223
7305
  }
@@ -8723,9 +8805,9 @@ var init_auth = __esm({
8723
8805
  });
8724
8806
 
8725
8807
  // src/cli.ts
8726
- var import_node_path11 = __toESM(require("node:path"));
8727
- var import_node_fs9 = __toESM(require("node:fs"));
8728
- var import_node_child_process4 = require("node:child_process");
8808
+ var import_node_path12 = __toESM(require("node:path"));
8809
+ var import_node_fs10 = __toESM(require("node:fs"));
8810
+ var import_node_child_process5 = require("node:child_process");
8729
8811
 
8730
8812
  // src/core/electron-install.ts
8731
8813
  var import_node_fs = __toESM(require("node:fs"));
@@ -9258,21 +9340,21 @@ function findSessionInfo(storage, sessionID) {
9258
9340
  const key = `${storage}|${sessionID}`;
9259
9341
  const cached = sessionCache.get(key);
9260
9342
  if (cached && (cached.info || Date.now() - cached.at < 6e4)) return cached.info;
9261
- let info = null;
9343
+ let info2 = null;
9262
9344
  const sessionDir = import_node_path7.default.join(storage, "session");
9263
9345
  const direct = import_node_path7.default.join(sessionDir, "info", `${sessionID}.json`);
9264
- if (import_node_fs5.default.existsSync(direct)) info = readJsonFile(direct);
9265
- if (!info) {
9346
+ if (import_node_fs5.default.existsSync(direct)) info2 = readJsonFile(direct);
9347
+ if (!info2) {
9266
9348
  for (const project of listDirs(sessionDir)) {
9267
9349
  const p = import_node_path7.default.join(sessionDir, project, `${sessionID}.json`);
9268
9350
  if (import_node_fs5.default.existsSync(p)) {
9269
- info = readJsonFile(p);
9351
+ info2 = readJsonFile(p);
9270
9352
  break;
9271
9353
  }
9272
9354
  }
9273
9355
  }
9274
- sessionCache.set(key, { at: Date.now(), info });
9275
- return info;
9356
+ sessionCache.set(key, { at: Date.now(), info: info2 });
9357
+ return info2;
9276
9358
  }
9277
9359
  function openaiIsOAuth(dataDir) {
9278
9360
  const file = import_node_path7.default.join(dataDir, "auth.json");
@@ -9342,16 +9424,16 @@ var opencodeSource = {
9342
9424
  provider,
9343
9425
  usage: { input, cached, cacheWrite, output, reasoning: t2.reasoning ?? 0, total: input + output, requests: 1 }
9344
9426
  };
9345
- const info = storage ? findSessionInfo(storage, m.sessionID) : null;
9346
- const cwd = info?.directory ?? null;
9427
+ const info2 = storage ? findSessionInfo(storage, m.sessionID) : null;
9428
+ const cwd = info2?.directory ?? null;
9347
9429
  return {
9348
9430
  sessionId: m.sessionID,
9349
9431
  agent: file.root.agent,
9350
9432
  provider,
9351
- startedAt: info?.time?.created ?? ts2,
9433
+ startedAt: info2?.time?.created ?? ts2,
9352
9434
  lastActivityAt: ts2,
9353
9435
  cwd,
9354
- projectName: projectNameOf(cwd) ?? info?.title ?? null,
9436
+ projectName: projectNameOf(cwd) ?? info2?.title ?? null,
9355
9437
  originator: "opencode",
9356
9438
  source: "opencode",
9357
9439
  cliVersion: null,
@@ -9784,7 +9866,20 @@ var SessionStore = class {
9784
9866
  // src/core/stats.ts
9785
9867
  init_src();
9786
9868
  var LIVE_WINDOW_MS = 5 * 60 * 1e3;
9869
+ var RATE_WINDOW_MS = 6e4;
9870
+ var BURST_WINDOW_MS = 1e4;
9787
9871
  var DAY3 = 864e5;
9872
+ function outputRate(events, now, windowMs, sessionStart) {
9873
+ const from = now - windowMs;
9874
+ let output = 0;
9875
+ for (const e of events) {
9876
+ if (e.ts <= from) continue;
9877
+ output += e.usage.output;
9878
+ }
9879
+ if (output <= 0) return 0;
9880
+ const elapsedSec = Math.max(1, (now - Math.max(from, sessionStart)) / 1e3);
9881
+ return output / elapsedSec;
9882
+ }
9788
9883
  var PriceCache = class {
9789
9884
  constructor(overrides) {
9790
9885
  this.overrides = overrides;
@@ -9861,7 +9956,9 @@ function agentStats(sessions, since, prices) {
9861
9956
  function computeStats(input) {
9862
9957
  const now = input.now ?? Date.now();
9863
9958
  const prices = new PriceCache(input.pricing);
9864
- const sessions = input.sessions;
9959
+ const sessions = input.sessions.map(
9960
+ (s) => s.events.every((e) => isOpenAIModel(e.model)) ? s : { ...s, events: s.events.filter((e) => isOpenAIModel(e.model)) }
9961
+ );
9865
9962
  const allEvents = [];
9866
9963
  const sessionCosts = /* @__PURE__ */ new Map();
9867
9964
  let lastActivityAt = null;
@@ -9887,14 +9984,8 @@ function computeStats(input) {
9887
9984
  let live = null;
9888
9985
  const liveSession = sessions.filter((s) => s.events.length && now - s.lastActivityAt <= LIVE_WINDOW_MS).sort((a, b) => b.lastActivityAt - a.lastActivityAt)[0];
9889
9986
  if (liveSession) {
9890
- let t60 = 0;
9891
- let t10 = 0;
9892
- for (const e of liveSession.events) {
9893
- const age = now - e.ts;
9894
- if (age <= 6e4) t60 += e.usage.total;
9895
- if (age <= 1e4) t10 += e.usage.total;
9896
- }
9897
9987
  const last = liveSession.events[liveSession.events.length - 1];
9988
+ const start = liveSession.startedAt || liveSession.events[0].ts;
9898
9989
  live = {
9899
9990
  sessionId: liveSession.sessionId,
9900
9991
  agent: liveSession.agent,
@@ -9902,8 +9993,8 @@ function computeStats(input) {
9902
9993
  model: liveSession.model,
9903
9994
  startedAt: liveSession.startedAt,
9904
9995
  lastEventAt: last.ts,
9905
- tokensPerSecond: t60 / 60,
9906
- tokensPerSecond10s: t10 / 10,
9996
+ tokensPerSecond: outputRate(liveSession.events, now, RATE_WINDOW_MS, start),
9997
+ tokensPerSecond10s: outputRate(liveSession.events, now, BURST_WINDOW_MS, start),
9907
9998
  contextUsed: last.usage.input + last.usage.output,
9908
9999
  contextWindow: liveSession.contextWindow,
9909
10000
  sessionUsage: { ...liveSession.cumulative },
@@ -9930,6 +10021,7 @@ function computeStats(input) {
9930
10021
  }
9931
10022
  return {
9932
10023
  buckets,
10024
+ sessions: sessions.filter((s) => s.events.length),
9933
10025
  today,
9934
10026
  week,
9935
10027
  month,
@@ -10010,8 +10102,173 @@ async function fetchLiveRateLimits(appVersion, timeoutMs = 1e4) {
10010
10102
  // src/core/engine.ts
10011
10103
  init_i18n();
10012
10104
 
10105
+ // src/core/update.ts
10106
+ var import_node_fs9 = __toESM(require("node:fs"));
10107
+ var import_node_path11 = __toESM(require("node:path"));
10108
+ var import_node_child_process4 = require("node:child_process");
10109
+ init_config();
10110
+
10013
10111
  // src/version.ts
10014
- var APP_VERSION = true ? "0.1.1" : "0.0.0-dev";
10112
+ var APP_VERSION = true ? "0.2.0" : "0.0.0-dev";
10113
+
10114
+ // src/core/update.ts
10115
+ var NPM_PACKAGE = "codex-token-tracker";
10116
+ var CHECK_TTL_MS = 6 * 60 * 60 * 1e3;
10117
+ var REQUEST_TIMEOUT_MS = 8e3;
10118
+ var INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
10119
+ var LOG_TAIL_CHARS = 4e3;
10120
+ function parseVersion(v2) {
10121
+ const s = String(v2 ?? "").trim().replace(/^v/, "");
10122
+ const i2 = s.indexOf("-");
10123
+ const core = i2 === -1 ? s : s.slice(0, i2);
10124
+ const pre = i2 === -1 ? "" : s.slice(i2 + 1);
10125
+ const n = core.split(".").map((x) => Number.parseInt(x, 10) || 0);
10126
+ return { nums: [n[0] ?? 0, n[1] ?? 0, n[2] ?? 0], pre };
10127
+ }
10128
+ function compareVersions(a, b) {
10129
+ const A = parseVersion(a);
10130
+ const B = parseVersion(b);
10131
+ for (let i2 = 0; i2 < 3; i2++) if (A.nums[i2] !== B.nums[i2]) return A.nums[i2] < B.nums[i2] ? -1 : 1;
10132
+ if (A.pre === B.pre) return 0;
10133
+ if (!A.pre) return 1;
10134
+ if (!B.pre) return -1;
10135
+ return A.pre < B.pre ? -1 : 1;
10136
+ }
10137
+ function isDevBuild(version2) {
10138
+ return version2.startsWith("0.0.0");
10139
+ }
10140
+ function registryBase() {
10141
+ const r = process.env.CODEX_TRACKER_REGISTRY || process.env.npm_config_registry || process.env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org";
10142
+ return r.replace(/\/+$/, "");
10143
+ }
10144
+ async function fetchLatestVersion(signal) {
10145
+ const ctrl = new AbortController();
10146
+ const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
10147
+ signal?.addEventListener("abort", () => ctrl.abort(), { once: true });
10148
+ try {
10149
+ const res = await fetch(`${registryBase()}/-/package/${NPM_PACKAGE}/dist-tags`, {
10150
+ signal: ctrl.signal,
10151
+ headers: { accept: "application/json" }
10152
+ });
10153
+ if (!res.ok) throw new Error(`registry responded ${res.status}`);
10154
+ const json = await res.json();
10155
+ const latest = json?.latest;
10156
+ if (typeof latest !== "string" || !latest) throw new Error("registry returned no `latest` tag");
10157
+ return latest;
10158
+ } finally {
10159
+ clearTimeout(timer);
10160
+ }
10161
+ }
10162
+ function detectPackageManager(installDir = import_node_path11.default.resolve(__dirname, "..")) {
10163
+ const p = installDir.replace(/\\/g, "/").toLowerCase();
10164
+ if (/\/\.?bun\//.test(p)) return "bun";
10165
+ if (/\/\.?pnpm[/-]/.test(p)) return "pnpm";
10166
+ if (/\/\.?yarn\//.test(p)) return "yarn";
10167
+ const ua = (process.env.npm_config_user_agent ?? "").toLowerCase();
10168
+ if (ua.startsWith("pnpm")) return "pnpm";
10169
+ if (ua.startsWith("yarn")) return "yarn";
10170
+ if (ua.startsWith("bun")) return "bun";
10171
+ return "npm";
10172
+ }
10173
+ function updateArgs(pm, spec = `${NPM_PACKAGE}@latest`) {
10174
+ switch (pm) {
10175
+ case "pnpm":
10176
+ return ["add", "-g", spec];
10177
+ case "yarn":
10178
+ return ["global", "add", spec];
10179
+ case "bun":
10180
+ return ["add", "-g", spec];
10181
+ default:
10182
+ return ["install", "-g", spec];
10183
+ }
10184
+ }
10185
+ function updateCommand(pm, spec) {
10186
+ return `${pm} ${updateArgs(pm, spec).join(" ")}`;
10187
+ }
10188
+ function cachePath() {
10189
+ return import_node_path11.default.join(configDir(), "update.json");
10190
+ }
10191
+ function readCache() {
10192
+ try {
10193
+ const raw = JSON.parse(import_node_fs9.default.readFileSync(cachePath(), "utf8"));
10194
+ if (typeof raw?.latest === "string" && typeof raw.checkedAt === "number") {
10195
+ return { latest: raw.latest, checkedAt: raw.checkedAt };
10196
+ }
10197
+ } catch {
10198
+ }
10199
+ return null;
10200
+ }
10201
+ function writeCache(c) {
10202
+ try {
10203
+ import_node_fs9.default.mkdirSync(import_node_path11.default.dirname(cachePath()), { recursive: true });
10204
+ import_node_fs9.default.writeFileSync(cachePath(), JSON.stringify(c, null, 2));
10205
+ } catch {
10206
+ }
10207
+ }
10208
+ function info(current, latest, checkedAt, error) {
10209
+ const pm = detectPackageManager();
10210
+ return {
10211
+ current,
10212
+ latest,
10213
+ available: Boolean(latest) && !isDevBuild(current) && compareVersions(latest, current) > 0,
10214
+ checkedAt,
10215
+ error,
10216
+ packageManager: pm,
10217
+ command: updateCommand(pm)
10218
+ };
10219
+ }
10220
+ async function checkForUpdate(opts = {}) {
10221
+ const current = opts.current ?? APP_VERSION;
10222
+ const cached = readCache();
10223
+ if (!opts.force && cached && Date.now() - cached.checkedAt < CHECK_TTL_MS) {
10224
+ return info(current, cached.latest, cached.checkedAt, null);
10225
+ }
10226
+ try {
10227
+ const latest = await fetchLatestVersion(opts.signal);
10228
+ const checkedAt = Date.now();
10229
+ writeCache({ latest, checkedAt });
10230
+ return info(current, latest, checkedAt, null);
10231
+ } catch (err) {
10232
+ return info(current, cached?.latest ?? null, cached?.checkedAt ?? null, err.message);
10233
+ }
10234
+ }
10235
+ function runUpdate(opts = {}) {
10236
+ const pm = detectPackageManager();
10237
+ const spec = opts.version ? `${NPM_PACKAGE}@${opts.version}` : `${NPM_PACKAGE}@latest`;
10238
+ const args = updateArgs(pm, spec);
10239
+ const command = `${pm} ${args.join(" ")}`;
10240
+ return new Promise((resolve) => {
10241
+ let output = "";
10242
+ const collect = (chunk) => {
10243
+ const text = chunk.toString();
10244
+ opts.onOutput?.(text);
10245
+ output = (output + text).slice(-LOG_TAIL_CHARS);
10246
+ };
10247
+ let child;
10248
+ try {
10249
+ child = (0, import_node_child_process4.spawn)(pm, args, {
10250
+ // Electron's bundled Node has no shell PATH resolution for `npm.cmd` on Windows.
10251
+ shell: process.platform === "win32",
10252
+ env: { ...process.env, ELECTRON_RUN_AS_NODE: void 0 },
10253
+ windowsHide: true
10254
+ });
10255
+ } catch (err) {
10256
+ resolve({ ok: false, code: null, command, output: err.message });
10257
+ return;
10258
+ }
10259
+ const timer = setTimeout(() => child.kill(), INSTALL_TIMEOUT_MS);
10260
+ child.stdout?.on("data", collect);
10261
+ child.stderr?.on("data", collect);
10262
+ child.on("error", (err) => {
10263
+ clearTimeout(timer);
10264
+ resolve({ ok: false, code: null, command, output: output + err.message });
10265
+ });
10266
+ child.on("close", (code2) => {
10267
+ clearTimeout(timer);
10268
+ resolve({ ok: code2 === 0, code: code2, command, output });
10269
+ });
10270
+ });
10271
+ }
10015
10272
 
10016
10273
  // src/core/engine.ts
10017
10274
  var SHALLOW_MS = 3e3;
@@ -10020,6 +10277,7 @@ var TICK_MS = 2e3;
10020
10277
  var REMOTE_MS = 6e4;
10021
10278
  var LIVE_LIMITS_DEBOUNCE_MS = 1e4;
10022
10279
  var LIVE_LIMITS_MIN_GAP_MS = 2e4;
10280
+ var UPDATE_CHECK_MS = 6 * 60 * 60 * 1e3;
10023
10281
  var Engine = class extends import_node_events.EventEmitter {
10024
10282
  constructor(opts) {
10025
10283
  super();
@@ -10056,6 +10314,9 @@ var Engine = class extends import_node_events.EventEmitter {
10056
10314
  liveLimitsInFlight = false;
10057
10315
  liveLimitsTimer = null;
10058
10316
  lastLiveLimitsAttempt = 0;
10317
+ update = null;
10318
+ updateStatus = "idle";
10319
+ updateLog = null;
10059
10320
  get heatmapWeeks() {
10060
10321
  return this.opts.heatmapWeeks ?? 16;
10061
10322
  }
@@ -10077,6 +10338,8 @@ var Engine = class extends import_node_events.EventEmitter {
10077
10338
  return;
10078
10339
  }
10079
10340
  void this.refreshLiveLimits(true);
10341
+ void this.checkUpdate(false);
10342
+ this.timers.push(setInterval(() => void this.checkUpdate(false), UPDATE_CHECK_MS));
10080
10343
  this.store.startWatching(() => void this.refresh(false));
10081
10344
  this.timers.push(setInterval(() => void this.refresh(false), SHALLOW_MS));
10082
10345
  this.timers.push(setInterval(() => void this.refresh(true), DEEP_MS));
@@ -10183,7 +10446,7 @@ var Engine = class extends import_node_events.EventEmitter {
10183
10446
  if (!this.signedIn) return { buckets: 0, sessions: 0 };
10184
10447
  if (!this.stats) this.recompute();
10185
10448
  try {
10186
- const r = await this.uploader.pushAll(this.stats.buckets, this.store.sessions(), this.stats.sessionCosts);
10449
+ const r = await this.uploader.pushAll(this.stats.buckets, this.stats.sessions, this.stats.sessionCosts);
10187
10450
  this.emitSnapshot();
10188
10451
  return r;
10189
10452
  } catch (err) {
@@ -10207,6 +10470,48 @@ var Engine = class extends import_node_events.EventEmitter {
10207
10470
  await this.uploader.fetchRemote(this.heatmapWeeks);
10208
10471
  this.recompute();
10209
10472
  }
10473
+ /**
10474
+ * Ask the npm registry for the newest published version (cached for 6 h unless `force`).
10475
+ * Best-effort: a failure is recorded on the snapshot, never thrown.
10476
+ */
10477
+ async checkUpdate(force) {
10478
+ if (!this.config.checkUpdates) {
10479
+ this.update = null;
10480
+ return null;
10481
+ }
10482
+ if (this.updateStatus === "checking" || this.updateStatus === "installing") return this.update;
10483
+ this.updateStatus = "checking";
10484
+ this.emitSnapshot();
10485
+ this.update = await checkForUpdate({ force });
10486
+ this.updateStatus = "idle";
10487
+ if (this.update.error) this.opts.log?.(`update check failed: ${this.update.error}`);
10488
+ this.emitSnapshot();
10489
+ return this.update;
10490
+ }
10491
+ /**
10492
+ * Install the newest version globally with the package manager this copy came from.
10493
+ * The new code only takes effect once the app is restarted, so the UI says so rather than
10494
+ * pretending to hot-swap itself.
10495
+ */
10496
+ async installUpdate() {
10497
+ if (this.updateStatus === "installing") return false;
10498
+ if (!this.update?.available) await this.checkUpdate(true);
10499
+ if (!this.update?.available) return false;
10500
+ this.updateStatus = "installing";
10501
+ this.updateLog = null;
10502
+ this.emitSnapshot();
10503
+ const r = await runUpdate({ version: this.update.latest ?? void 0 });
10504
+ this.updateStatus = r.ok ? "installed" : "failed";
10505
+ this.updateLog = r.ok ? null : `${r.command}
10506
+ ${r.output}`.trim();
10507
+ if (!r.ok) this.opts.log?.(`update install failed (${r.code}): ${r.output}`);
10508
+ this.emitSnapshot();
10509
+ return r.ok;
10510
+ }
10511
+ updateState() {
10512
+ if (!this.config.checkUpdates || !this.update) return null;
10513
+ return { ...this.update, status: this.updateStatus, log: this.updateLog };
10514
+ }
10210
10515
  /** Start the device-code login flow (resolves when approved / denied / expired / cancelled). */
10211
10516
  async login(openBrowser, onCode) {
10212
10517
  if (this.pending) return { status: "cancelled" };
@@ -10299,6 +10604,7 @@ var Engine = class extends import_node_events.EventEmitter {
10299
10604
  rateLimits: this.liveLimits ?? fromLogRateLimits(s.logRateLimits),
10300
10605
  rateLimitsError: this.config.liveRateLimits ? this.liveLimitsError : null,
10301
10606
  rateLimitsUpdatedAt: this.liveLimits ? this.liveLimitsAt : s.logRateLimits?.observedAt ?? null,
10607
+ update: this.updateState(),
10302
10608
  modelsToday: s.modelsToday,
10303
10609
  modelsMonth: s.modelsMonth,
10304
10610
  byAgentToday: s.byAgentToday,
@@ -10357,14 +10663,14 @@ function applyDashboardFlag(flags) {
10357
10663
  function electronBinary() {
10358
10664
  try {
10359
10665
  const p = require("electron");
10360
- return typeof p === "string" && p && import_node_fs9.default.existsSync(p) ? p : null;
10666
+ return typeof p === "string" && p && import_node_fs10.default.existsSync(p) ? p : null;
10361
10667
  } catch {
10362
10668
  return null;
10363
10669
  }
10364
10670
  }
10365
10671
  function electronPackageDir() {
10366
10672
  try {
10367
- return import_node_path11.default.dirname(require.resolve("electron/package.json"));
10673
+ return import_node_path12.default.dirname(require.resolve("electron/package.json"));
10368
10674
  } catch {
10369
10675
  return null;
10370
10676
  }
@@ -10384,11 +10690,11 @@ async function ensureElectron() {
10384
10690
  log2.error(` ${err.message}`);
10385
10691
  }
10386
10692
  if (!bin) {
10387
- const installer = import_node_path11.default.join(dir, "install.js");
10388
- if (import_node_fs9.default.existsSync(installer)) {
10693
+ const installer = import_node_path12.default.join(dir, "install.js");
10694
+ if (import_node_fs10.default.existsSync(installer)) {
10389
10695
  const env = { ...process.env };
10390
10696
  delete env.ELECTRON_SKIP_BINARY_DOWNLOAD;
10391
- (0, import_node_child_process4.spawnSync)(process.execPath, [installer], { stdio: "inherit", cwd: dir, env });
10697
+ (0, import_node_child_process5.spawnSync)(process.execPath, [installer], { stdio: "inherit", cwd: dir, env });
10392
10698
  }
10393
10699
  }
10394
10700
  try {
@@ -10408,9 +10714,9 @@ async function startMenubar(background) {
10408
10714
  }
10409
10715
  const env = { ...process.env, CODEX_TRACKER_HOME: configDir() };
10410
10716
  delete env.ELECTRON_RUN_AS_NODE;
10411
- const mainPath = import_node_path11.default.join(__dirname, "main.js");
10717
+ const mainPath = import_node_path12.default.join(__dirname, "main.js");
10412
10718
  console.log(t2("cliStartingMenubar"));
10413
- const child = (0, import_node_child_process4.spawn)(bin, [mainPath], {
10719
+ const child = (0, import_node_child_process5.spawn)(bin, [mainPath], {
10414
10720
  stdio: background ? "ignore" : "inherit",
10415
10721
  detached: background,
10416
10722
  env,
@@ -10493,6 +10799,10 @@ async function runStatus(json) {
10493
10799
  line(t2("cliStatusDirs"), s.sessionDirs.join("\n" + " ".repeat(15)) || "-");
10494
10800
  const byAgent = Object.entries(s.counts.byAgent).sort((a, b) => b[1].sessions - a[1].sessions).map(([agent, c]) => `${agent} ${c.sessions}`).join(", ");
10495
10801
  console.log(t2("sessions", { n: s.counts.sessions }) + " \xB7 " + t2("files", { n: s.counts.files }) + (byAgent ? ` (${byAgent})` : ""));
10802
+ if (cfg.checkUpdates) {
10803
+ const u = await checkForUpdate();
10804
+ if (u.available) console.log("\n" + t2("cliUpdateAvailable", { current: u.current, latest: u.latest ?? "?" }) + ` \u2014 ${u.command}`);
10805
+ }
10496
10806
  return 0;
10497
10807
  }
10498
10808
  async function runLogin(flags) {
@@ -10642,6 +10952,31 @@ function runConfig(positional) {
10642
10952
  console.log(t2("cliConfigDir", { dir: configDir() }));
10643
10953
  return 0;
10644
10954
  }
10955
+ async function runUpdateCommand(flags) {
10956
+ const t2 = makeT(lang());
10957
+ const info2 = await checkForUpdate({ force: true });
10958
+ if (info2.error && !info2.latest) {
10959
+ console.error(t2("cliUpdateCheckFailed", { message: info2.error }));
10960
+ return 1;
10961
+ }
10962
+ if (!info2.available) {
10963
+ console.log(t2("cliUpdateLatest", { version: info2.current }));
10964
+ return 0;
10965
+ }
10966
+ console.log(t2("cliUpdateAvailable", { current: info2.current, latest: info2.latest ?? "?" }));
10967
+ if (flags.check === true) {
10968
+ console.log(` ${info2.command}`);
10969
+ return 0;
10970
+ }
10971
+ console.log(t2("cliUpdateRunning", { command: info2.command }));
10972
+ const r = await runUpdate({ version: info2.latest ?? void 0, onOutput: (c) => process.stdout.write(c) });
10973
+ if (!r.ok) {
10974
+ console.error(t2("cliUpdateFailed", { code: r.code ?? "?", command: r.command }));
10975
+ return 1;
10976
+ }
10977
+ console.log(t2("cliUpdateDone", { version: info2.latest ?? "?" }));
10978
+ return 0;
10979
+ }
10645
10980
  function runLang(positional) {
10646
10981
  const value = positional[0];
10647
10982
  if (!value || !["en", "zh", "auto"].includes(value)) {
@@ -10699,6 +11034,9 @@ async function main() {
10699
11034
  return runConfig(args.positional);
10700
11035
  case "lang":
10701
11036
  return runLang(args.positional);
11037
+ case "update":
11038
+ case "upgrade":
11039
+ return runUpdateCommand(args.flags);
10702
11040
  default:
10703
11041
  console.error(t2("cliUnknownCommand", { command: args.command }));
10704
11042
  console.log(t2("cliUsage"));