codex-token-tracker 0.1.0 → 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;
@@ -1101,11 +1140,11 @@ var init_src = __esm({
1101
1140
 
1102
1141
  // src/core/config.ts
1103
1142
  function configDir() {
1104
- return process.env.CODEX_TRACKER_HOME || import_node_path.default.join(import_node_os.default.homedir(), ".codex-tracker");
1143
+ return process.env.CODEX_TRACKER_HOME || import_node_path2.default.join(import_node_os2.default.homedir(), ".codex-tracker");
1105
1144
  }
1106
1145
  function readJson(file, fallback) {
1107
1146
  try {
1108
- const raw = import_node_fs.default.readFileSync(file, "utf8");
1147
+ const raw = import_node_fs2.default.readFileSync(file, "utf8");
1109
1148
  const parsed = JSON.parse(raw);
1110
1149
  return parsed && typeof parsed === "object" ? parsed : fallback;
1111
1150
  } catch {
@@ -1113,13 +1152,13 @@ function readJson(file, fallback) {
1113
1152
  }
1114
1153
  }
1115
1154
  function writeJsonAtomic(file, value) {
1116
- import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
1155
+ import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(file), { recursive: true });
1117
1156
  const tmp = `${file}.${process.pid}.tmp`;
1118
- import_node_fs.default.writeFileSync(tmp, JSON.stringify(value, null, 2), { mode: 384 });
1119
- import_node_fs.default.renameSync(tmp, file);
1157
+ import_node_fs2.default.writeFileSync(tmp, JSON.stringify(value, null, 2), { mode: 384 });
1158
+ import_node_fs2.default.renameSync(tmp, file);
1120
1159
  }
1121
1160
  function configPath() {
1122
- return import_node_path.default.join(configDir(), "config.json");
1161
+ return import_node_path2.default.join(configDir(), "config.json");
1123
1162
  }
1124
1163
  function parseBool(raw) {
1125
1164
  return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
@@ -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();
@@ -1224,7 +1265,7 @@ function coerceConfigValue(key, raw) {
1224
1265
  }
1225
1266
  }
1226
1267
  function statePath() {
1227
- return import_node_path.default.join(configDir(), "state.json");
1268
+ return import_node_path2.default.join(configDir(), "state.json");
1228
1269
  }
1229
1270
  function loadState() {
1230
1271
  const s = readJson(statePath(), {});
@@ -1241,7 +1282,7 @@ function clearState() {
1241
1282
  saveState({ pushedBuckets: {}, pushedSessions: {}, lastUploadAt: null });
1242
1283
  }
1243
1284
  function pricingPath() {
1244
- return import_node_path.default.join(configDir(), "pricing.json");
1285
+ return import_node_path2.default.join(configDir(), "pricing.json");
1245
1286
  }
1246
1287
  function loadPricingOverrides() {
1247
1288
  const raw = readJson(pricingPath(), {});
@@ -1259,13 +1300,13 @@ function loadPricingOverrides() {
1259
1300
  }
1260
1301
  return Object.keys(out).length ? out : void 0;
1261
1302
  }
1262
- var import_node_fs, import_node_os, import_node_path, DEFAULT_DASHBOARD_URL, SOURCE_FORMATS, DEFAULT_SOURCES, SOURCE_IDS, DEFAULT_CONFIG, EDITABLE_KEYS, AGENT_NAME_RE;
1303
+ var import_node_fs2, import_node_os2, import_node_path2, DEFAULT_DASHBOARD_URL, SOURCE_FORMATS, DEFAULT_SOURCES, SOURCE_IDS, DEFAULT_CONFIG, EDITABLE_KEYS, AGENT_NAME_RE;
1263
1304
  var init_config = __esm({
1264
1305
  "src/core/config.ts"() {
1265
1306
  "use strict";
1266
- import_node_fs = __toESM(require("node:fs"));
1267
- import_node_os = __toESM(require("node:os"));
1268
- import_node_path = __toESM(require("node:path"));
1307
+ import_node_fs2 = __toESM(require("node:fs"));
1308
+ import_node_os2 = __toESM(require("node:os"));
1309
+ import_node_path2 = __toESM(require("node:path"));
1269
1310
  DEFAULT_DASHBOARD_URL = "https://codex.chenli.dev";
1270
1311
  SOURCE_FORMATS = ["codex", "pi", "generic", "opencode", "cline"];
1271
1312
  DEFAULT_SOURCES = { codex: true, pi: true, hermes: true, opencode: true, cline: true, roo: true, kilo: true };
@@ -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
@@ -1404,6 +1461,8 @@ Options:
1404
1461
  cliUnknownCommand: "Unknown command: {command}",
1405
1462
  cliNoDisplay: "No display detected \u2014 starting agent mode (run `codex-tracker menubar` on a desktop).",
1406
1463
  cliNoElectron: "Electron is not installed (optional dependency) \u2014 starting agent mode.",
1464
+ cliDownloadingElectron: "Electron runtime not found \u2014 downloading it now (about 100 MB, one time)\u2026",
1465
+ cliElectronDownloadFailed: "Electron download failed. Retry with `npm install -g codex-token-tracker --allow-scripts=electron` or `npm rebuild -g electron`; the headless `codex-tracker agent` works without it.",
1407
1466
  cliStartingMenubar: "Starting menu bar app\u2026",
1408
1467
  cliLoginStart: "Connecting this device to {dashboard}",
1409
1468
  cliLoginCode: "Your code: {code}",
@@ -1435,7 +1494,13 @@ Options:
1435
1494
  cliConfigSet: "Set {key} = {value}",
1436
1495
  cliConfigUnknownKey: "Unknown config key: {key}. Keys: {keys}",
1437
1496
  cliLangSet: "Language set to {lang}",
1438
- 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}"
1439
1504
  };
1440
1505
  }
1441
1506
  });
@@ -1515,7 +1580,18 @@ var init_zh = __esm({
1515
1580
  never: "\u4ECE\u672A",
1516
1581
  local: "\u672C\u673A",
1517
1582
  remote: "\u5176\u4ED6\u8BBE\u5907",
1518
- 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
1519
1595
 
1520
1596
  \u547D\u4EE4\uFF1A
1521
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
@@ -1526,10 +1602,12 @@ var init_zh = __esm({
1526
1602
  logout \u65AD\u5F00\u672C\u8BBE\u5907
1527
1603
  status \u6253\u5370\u4ECA\u65E5\u7528\u91CF\u3001\u5B9E\u65F6\u4F1A\u8BDD\u4E0E\u9650\u989D
1528
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
1529
1606
  config get|set \u8BFB\u53D6\u6216\u4FEE\u6539\u8BBE\u7F6E\uFF08config set uploadIntervalSec 30\uFF0Cconfig set sources.pi false\uFF09
1530
1607
  lang <en|zh|auto> \u8BBE\u7F6E\u663E\u793A\u8BED\u8A00
1531
1608
 
1532
1609
  \u9009\u9879\uFF1A
1610
+ --check update\uFF1A\u53EA\u62A5\u544A\u6700\u65B0\u7248\u672C\uFF0C\u4E0D\u6267\u884C\u5B89\u88C5
1533
1611
  --dashboard <url> \u4EEA\u8868\u76D8\u5730\u5740\uFF08\u81EA\u6258\u7BA1\uFF09
1534
1612
  --background \u4EE5\u540E\u53F0\u65B9\u5F0F\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u5E76\u7ACB\u5373\u8FD4\u56DE
1535
1613
  --version, -v \u663E\u793A\u7248\u672C
@@ -1538,6 +1616,8 @@ var init_zh = __esm({
1538
1616
  cliUnknownCommand: "\u672A\u77E5\u547D\u4EE4\uFF1A{command}",
1539
1617
  cliNoDisplay: "\u672A\u68C0\u6D4B\u5230\u663E\u793A\u73AF\u5883 \u2014 \u4EE5 agent \u6A21\u5F0F\u542F\u52A8\uFF08\u684C\u9762\u73AF\u5883\u8BF7\u8FD0\u884C `codex-tracker menubar`\uFF09\u3002",
1540
1618
  cliNoElectron: "\u672A\u5B89\u88C5 Electron\uFF08\u53EF\u9009\u4F9D\u8D56\uFF09\u2014 \u4EE5 agent \u6A21\u5F0F\u542F\u52A8\u3002",
1619
+ cliDownloadingElectron: "\u672A\u627E\u5230 Electron \u8FD0\u884C\u65F6\uFF0C\u6B63\u5728\u4E0B\u8F7D\uFF08\u7EA6 100 MB\uFF0C\u4EC5\u9700\u4E00\u6B21\uFF09\u2026",
1620
+ cliElectronDownloadFailed: "Electron \u4E0B\u8F7D\u5931\u8D25\u3002\u53EF\u91CD\u8BD5 `npm install -g codex-token-tracker --allow-scripts=electron` \u6216 `npm rebuild -g electron`\uFF1B\u65E0\u754C\u9762\u7684 `codex-tracker agent` \u65E0\u9700 Electron\u3002",
1541
1621
  cliStartingMenubar: "\u6B63\u5728\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u2026",
1542
1622
  cliLoginStart: "\u6B63\u5728\u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230 {dashboard}",
1543
1623
  cliLoginCode: "\u4F60\u7684\u4EE3\u7801\uFF1A{code}",
@@ -1569,7 +1649,13 @@ var init_zh = __esm({
1569
1649
  cliConfigSet: "\u5DF2\u8BBE\u7F6E {key} = {value}",
1570
1650
  cliConfigUnknownKey: "\u672A\u77E5\u914D\u7F6E\u9879\uFF1A{key}\u3002\u53EF\u7528\uFF1A{keys}",
1571
1651
  cliLangSet: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A {lang}",
1572
- 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}"
1573
1659
  };
1574
1660
  }
1575
1661
  });
@@ -1646,7 +1732,7 @@ function isWSL() {
1646
1732
  if (process.platform !== "linux") return wslCache = false;
1647
1733
  if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return wslCache = true;
1648
1734
  try {
1649
- const v2 = import_node_fs2.default.readFileSync("/proc/version", "utf8").toLowerCase();
1735
+ const v2 = import_node_fs3.default.readFileSync("/proc/version", "utf8").toLowerCase();
1650
1736
  return wslCache = v2.includes("microsoft") || v2.includes("wsl");
1651
1737
  } catch {
1652
1738
  return wslCache = false;
@@ -1662,7 +1748,7 @@ function platformLabel(kind = platformKind()) {
1662
1748
  }
1663
1749
  function hostname() {
1664
1750
  try {
1665
- return import_node_os2.default.hostname().replace(/\.local$/, "");
1751
+ return import_node_os3.default.hostname().replace(/\.local$/, "");
1666
1752
  } catch {
1667
1753
  return "unknown-host";
1668
1754
  }
@@ -1680,7 +1766,7 @@ function hasDisplay() {
1680
1766
  function run(cmd, args) {
1681
1767
  return new Promise((resolve) => {
1682
1768
  try {
1683
- const child = (0, import_node_child_process.spawn)(cmd, args, { stdio: "ignore", detached: true, windowsHide: true });
1769
+ const child = (0, import_node_child_process2.spawn)(cmd, args, { stdio: "ignore", detached: true, windowsHide: true });
1684
1770
  child.on("error", () => resolve(false));
1685
1771
  child.on("spawn", () => {
1686
1772
  child.unref();
@@ -1713,13 +1799,13 @@ function systemLocale() {
1713
1799
  return "en";
1714
1800
  }
1715
1801
  }
1716
- var import_node_fs2, import_node_os2, import_node_child_process, wslCache;
1802
+ var import_node_fs3, import_node_os3, import_node_child_process2, wslCache;
1717
1803
  var init_platform = __esm({
1718
1804
  "src/core/platform.ts"() {
1719
1805
  "use strict";
1720
- import_node_fs2 = __toESM(require("node:fs"));
1721
- import_node_os2 = __toESM(require("node:os"));
1722
- import_node_child_process = require("node:child_process");
1806
+ import_node_fs3 = __toESM(require("node:fs"));
1807
+ import_node_os3 = __toESM(require("node:os"));
1808
+ import_node_child_process2 = require("node:child_process");
1723
1809
  init_i18n();
1724
1810
  wslCache = null;
1725
1811
  }
@@ -3190,12 +3276,12 @@ function createApi(pathParts = []) {
3190
3276
  `API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
3191
3277
  );
3192
3278
  }
3193
- const path11 = pathParts.slice(0, -1).join("/");
3279
+ const path13 = pathParts.slice(0, -1).join("/");
3194
3280
  const exportName = pathParts[pathParts.length - 1];
3195
3281
  if (exportName === "default") {
3196
- return path11;
3282
+ return path13;
3197
3283
  } else {
3198
- return path11 + ":" + exportName;
3284
+ return path13 + ":" + exportName;
3199
3285
  }
3200
3286
  } else if (prop === Symbol.toStringTag) {
3201
3287
  return "FunctionReference";
@@ -3490,16 +3576,16 @@ var init_simple_client_node = __esm({
3490
3576
  });
3491
3577
  require_node_gyp_build = __commonJS({
3492
3578
  "../node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports2, module2) {
3493
- var fs8 = __require("fs");
3494
- var path11 = __require("path");
3495
- var os7 = __require("os");
3579
+ var fs11 = __require("fs");
3580
+ var path13 = __require("path");
3581
+ var os8 = __require("os");
3496
3582
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
3497
3583
  var vars2 = process.config && process.config.variables || {};
3498
3584
  var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
3499
3585
  var abi = process.versions.modules;
3500
3586
  var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
3501
- var arch = process.env.npm_config_arch || os7.arch();
3502
- var platform = process.env.npm_config_platform || os7.platform();
3587
+ var arch = process.env.npm_config_arch || os8.arch();
3588
+ var platform = process.env.npm_config_platform || os8.platform();
3503
3589
  var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
3504
3590
  var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars2.arm_version) || "";
3505
3591
  var uv = (process.versions.uv || "").split(".")[0];
@@ -3508,21 +3594,21 @@ var init_simple_client_node = __esm({
3508
3594
  return runtimeRequire(load.resolve(dir));
3509
3595
  }
3510
3596
  load.resolve = load.path = function(dir) {
3511
- dir = path11.resolve(dir || ".");
3597
+ dir = path13.resolve(dir || ".");
3512
3598
  try {
3513
- var name = runtimeRequire(path11.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
3599
+ var name = runtimeRequire(path13.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
3514
3600
  if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
3515
3601
  } catch (err) {
3516
3602
  }
3517
3603
  if (!prebuildsOnly) {
3518
- var release = getFirst(path11.join(dir, "build/Release"), matchBuild);
3604
+ var release = getFirst(path13.join(dir, "build/Release"), matchBuild);
3519
3605
  if (release) return release;
3520
- var debug = getFirst(path11.join(dir, "build/Debug"), matchBuild);
3606
+ var debug = getFirst(path13.join(dir, "build/Debug"), matchBuild);
3521
3607
  if (debug) return debug;
3522
3608
  }
3523
3609
  var prebuild = resolve(dir);
3524
3610
  if (prebuild) return prebuild;
3525
- var nearby = resolve(path11.dirname(process.execPath));
3611
+ var nearby = resolve(path13.dirname(process.execPath));
3526
3612
  if (nearby) return nearby;
3527
3613
  var target = [
3528
3614
  "platform=" + platform,
@@ -3539,26 +3625,26 @@ var init_simple_client_node = __esm({
3539
3625
  ].filter(Boolean).join(" ");
3540
3626
  throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
3541
3627
  function resolve(dir2) {
3542
- var tuples = readdirSync(path11.join(dir2, "prebuilds")).map(parseTuple);
3628
+ var tuples = readdirSync(path13.join(dir2, "prebuilds")).map(parseTuple);
3543
3629
  var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
3544
3630
  if (!tuple) return;
3545
- var prebuilds = path11.join(dir2, "prebuilds", tuple.name);
3631
+ var prebuilds = path13.join(dir2, "prebuilds", tuple.name);
3546
3632
  var parsed = readdirSync(prebuilds).map(parseTags);
3547
3633
  var candidates = parsed.filter(matchTags(runtime, abi));
3548
3634
  var winner = candidates.sort(compareTags(runtime))[0];
3549
- if (winner) return path11.join(prebuilds, winner.file);
3635
+ if (winner) return path13.join(prebuilds, winner.file);
3550
3636
  }
3551
3637
  };
3552
3638
  function readdirSync(dir) {
3553
3639
  try {
3554
- return fs8.readdirSync(dir);
3640
+ return fs11.readdirSync(dir);
3555
3641
  } catch (err) {
3556
3642
  return [];
3557
3643
  }
3558
3644
  }
3559
3645
  function getFirst(dir, filter) {
3560
3646
  var files = readdirSync(dir).filter(filter);
3561
- return files[0] && path11.join(dir, files[0]);
3647
+ return files[0] && path13.join(dir, files[0]);
3562
3648
  }
3563
3649
  function matchBuild(name) {
3564
3650
  return /\.node$/.test(name);
@@ -3645,7 +3731,7 @@ var init_simple_client_node = __esm({
3645
3731
  return typeof window !== "undefined" && window.process && window.process.type === "renderer";
3646
3732
  }
3647
3733
  function isAlpine(platform2) {
3648
- return platform2 === "linux" && fs8.existsSync("/etc/alpine-release");
3734
+ return platform2 === "linux" && fs11.existsSync("/etc/alpine-release");
3649
3735
  }
3650
3736
  load.parseTags = parseTags;
3651
3737
  load.matchTags = matchTags;
@@ -7191,13 +7277,13 @@ var init_simple_client_node = __esm({
7191
7277
  }
7192
7278
  }
7193
7279
  if (this.options.verifyClient) {
7194
- const info = {
7280
+ const info2 = {
7195
7281
  origin: req.headers[`${version2 === 8 ? "sec-websocket-origin" : "origin"}`],
7196
7282
  secure: !!(req.socket.authorized || req.socket.encrypted),
7197
7283
  req
7198
7284
  };
7199
7285
  if (this.options.verifyClient.length === 2) {
7200
- this.options.verifyClient(info, (verified, code2, message, headers) => {
7286
+ this.options.verifyClient(info2, (verified, code2, message, headers) => {
7201
7287
  if (!verified) {
7202
7288
  return abortHandshake(socket, code2 || 401, message, headers);
7203
7289
  }
@@ -7213,7 +7299,7 @@ var init_simple_client_node = __esm({
7213
7299
  });
7214
7300
  return;
7215
7301
  }
7216
- if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
7302
+ if (!this.options.verifyClient(info2)) return abortHandshake(socket, 401);
7217
7303
  }
7218
7304
  this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
7219
7305
  }
@@ -8719,8 +8805,158 @@ var init_auth = __esm({
8719
8805
  });
8720
8806
 
8721
8807
  // src/cli.ts
8722
- var import_node_path10 = __toESM(require("node:path"));
8723
- var import_node_child_process3 = 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");
8811
+
8812
+ // src/core/electron-install.ts
8813
+ var import_node_fs = __toESM(require("node:fs"));
8814
+ var import_node_os = __toESM(require("node:os"));
8815
+ var import_node_path = __toESM(require("node:path"));
8816
+ var import_node_child_process = require("node:child_process");
8817
+ function archName() {
8818
+ switch (process.arch) {
8819
+ case "x64":
8820
+ return "x64";
8821
+ case "arm64":
8822
+ return "arm64";
8823
+ case "ia32":
8824
+ return "ia32";
8825
+ case "arm":
8826
+ return "armv7l";
8827
+ default:
8828
+ return process.arch;
8829
+ }
8830
+ }
8831
+ function platformExecutablePath() {
8832
+ switch (process.platform) {
8833
+ case "darwin":
8834
+ return "Electron.app/Contents/MacOS/Electron";
8835
+ case "win32":
8836
+ return "electron.exe";
8837
+ default:
8838
+ return "electron";
8839
+ }
8840
+ }
8841
+ function electronCacheRoots() {
8842
+ const roots = [];
8843
+ if (process.env.electron_config_cache) roots.push(process.env.electron_config_cache);
8844
+ const home = import_node_os.default.homedir();
8845
+ if (process.platform === "darwin") roots.push(import_node_path.default.join(home, "Library", "Caches", "electron"));
8846
+ else if (process.platform === "win32") {
8847
+ if (process.env.LOCALAPPDATA) roots.push(import_node_path.default.join(process.env.LOCALAPPDATA, "electron", "Cache"));
8848
+ } else roots.push(import_node_path.default.join(process.env.XDG_CACHE_HOME ?? import_node_path.default.join(home, ".cache"), "electron"));
8849
+ return roots.filter((r) => import_node_fs.default.existsSync(r));
8850
+ }
8851
+ function findInDirs(dirs, filename, depth = 3) {
8852
+ for (const dir of dirs) {
8853
+ const found = walk(dir, filename, depth);
8854
+ if (found) return found;
8855
+ }
8856
+ return null;
8857
+ }
8858
+ function walk(dir, filename, depth) {
8859
+ let entries;
8860
+ try {
8861
+ entries = import_node_fs.default.readdirSync(dir, { withFileTypes: true });
8862
+ } catch {
8863
+ return null;
8864
+ }
8865
+ for (const e of entries) {
8866
+ const p = import_node_path.default.join(dir, e.name);
8867
+ if (e.isFile() && e.name === filename && import_node_fs.default.statSync(p).size > 1e6) return p;
8868
+ }
8869
+ if (depth <= 0) return null;
8870
+ for (const e of entries) {
8871
+ if (e.isDirectory()) {
8872
+ const r = walk(import_node_path.default.join(dir, e.name), filename, depth - 1);
8873
+ if (r) return r;
8874
+ }
8875
+ }
8876
+ return null;
8877
+ }
8878
+ function downloadUrl(version2, filename) {
8879
+ const mirror = process.env.ELECTRON_MIRROR || "https://github.com/electron/electron/releases/download/v";
8880
+ const dir = process.env.ELECTRON_CUSTOM_DIR ? process.env.ELECTRON_CUSTOM_DIR.replace("{{ version }}", version2) : version2;
8881
+ const base = mirror.endsWith("/") || mirror.endsWith("v") ? mirror : mirror + "/";
8882
+ return `${base}${dir}/${filename}`;
8883
+ }
8884
+ async function download(url, dest, log2) {
8885
+ const res = await fetch(url, { redirect: "follow" });
8886
+ if (!res.ok || !res.body) throw new Error(`HTTP ${res.status} for ${url}`);
8887
+ const total = Number(res.headers.get("content-length") || 0);
8888
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(dest), { recursive: true });
8889
+ const tmp = dest + ".part";
8890
+ const out = import_node_fs.default.createWriteStream(tmp);
8891
+ let received = 0;
8892
+ let lastPct = -10;
8893
+ const reader = res.body.getReader();
8894
+ try {
8895
+ for (; ; ) {
8896
+ const { done, value } = await reader.read();
8897
+ if (done) break;
8898
+ received += value.byteLength;
8899
+ if (!out.write(value)) await new Promise((r) => out.once("drain", () => r()));
8900
+ if (total) {
8901
+ const pct = Math.floor(received / total * 100);
8902
+ if (pct >= lastPct + 10) {
8903
+ lastPct = pct;
8904
+ log2.info(` ${pct}% (${(received / 1048576).toFixed(0)} / ${(total / 1048576).toFixed(0)} MB)`);
8905
+ }
8906
+ }
8907
+ }
8908
+ } finally {
8909
+ await new Promise((resolve) => out.end(() => resolve()));
8910
+ }
8911
+ import_node_fs.default.renameSync(tmp, dest);
8912
+ }
8913
+ function extractZip(zip, dest) {
8914
+ import_node_fs.default.rmSync(dest, { recursive: true, force: true });
8915
+ import_node_fs.default.mkdirSync(dest, { recursive: true });
8916
+ const attempts = process.platform === "darwin" ? [["ditto", ["-x", "-k", zip, dest]], ["unzip", ["-q", "-o", zip, "-d", dest]]] : process.platform === "win32" ? [
8917
+ ["tar", ["-xf", zip, "-C", dest]],
8918
+ ["powershell", ["-NoProfile", "-Command", `Expand-Archive -LiteralPath '${zip}' -DestinationPath '${dest}' -Force`]]
8919
+ ] : [["unzip", ["-q", "-o", zip, "-d", dest]], ["bsdtar", ["-xf", zip, "-C", dest]], ["tar", ["-xf", zip, "-C", dest]]];
8920
+ for (const [cmd, args] of attempts) {
8921
+ const r = (0, import_node_child_process.spawnSync)(cmd, args, { stdio: "ignore", windowsHide: true });
8922
+ if (r.status === 0 && import_node_fs.default.existsSync(import_node_path.default.join(dest, platformExecutablePath()))) return true;
8923
+ }
8924
+ return false;
8925
+ }
8926
+ async function installElectronBinary(pkgDir, log2) {
8927
+ let version2;
8928
+ try {
8929
+ version2 = JSON.parse(import_node_fs.default.readFileSync(import_node_path.default.join(pkgDir, "package.json"), "utf8")).version;
8930
+ } catch {
8931
+ return null;
8932
+ }
8933
+ const platform = process.platform === "darwin" && process.env.npm_config_platform === "mas" ? "mas" : process.platform;
8934
+ const filename = `electron-v${version2}-${platform}-${archName()}.zip`;
8935
+ const dist = import_node_path.default.join(pkgDir, "dist");
8936
+ const exe = import_node_path.default.join(dist, platformExecutablePath());
8937
+ let zip = findInDirs(electronCacheRoots(), filename);
8938
+ if (zip) log2.info(` using cached ${import_node_path.default.basename(zip)}`);
8939
+ else {
8940
+ const cacheRoot = electronCacheRoots()[0] ?? import_node_path.default.join(import_node_os.default.tmpdir(), "codex-tracker-electron");
8941
+ zip = import_node_path.default.join(cacheRoot, "codex-token-tracker", filename);
8942
+ const url = downloadUrl(version2, filename);
8943
+ log2.info(` ${url}`);
8944
+ try {
8945
+ await download(url, zip, log2);
8946
+ } catch (err) {
8947
+ log2.error(` download failed: ${err.message}`);
8948
+ return null;
8949
+ }
8950
+ }
8951
+ if (!extractZip(zip, dist)) {
8952
+ log2.error(" extraction failed (no ditto/unzip/tar available?)");
8953
+ return null;
8954
+ }
8955
+ import_node_fs.default.writeFileSync(import_node_path.default.join(pkgDir, "path.txt"), platformExecutablePath());
8956
+ return import_node_fs.default.existsSync(exe) ? exe : null;
8957
+ }
8958
+
8959
+ // src/cli.ts
8724
8960
  init_src();
8725
8961
  init_config();
8726
8962
 
@@ -8730,42 +8966,42 @@ init_src();
8730
8966
  init_config();
8731
8967
 
8732
8968
  // src/core/store.ts
8733
- var import_node_fs6 = __toESM(require("node:fs"));
8969
+ var import_node_fs7 = __toESM(require("node:fs"));
8734
8970
 
8735
8971
  // src/core/sources/index.ts
8736
- var import_node_fs5 = __toESM(require("node:fs"));
8737
- var import_node_os6 = __toESM(require("node:os"));
8738
- var import_node_path8 = __toESM(require("node:path"));
8739
- var import_node_child_process2 = require("node:child_process");
8972
+ var import_node_fs6 = __toESM(require("node:fs"));
8973
+ var import_node_os7 = __toESM(require("node:os"));
8974
+ var import_node_path9 = __toESM(require("node:path"));
8975
+ var import_node_child_process3 = require("node:child_process");
8740
8976
  init_platform();
8741
8977
  init_config();
8742
8978
 
8743
8979
  // src/core/sources/codex.ts
8744
- var import_node_os3 = __toESM(require("node:os"));
8745
- var import_node_path3 = __toESM(require("node:path"));
8980
+ var import_node_os4 = __toESM(require("node:os"));
8981
+ var import_node_path4 = __toESM(require("node:path"));
8746
8982
  init_src();
8747
8983
 
8748
8984
  // src/core/sources/util.ts
8749
- var import_node_fs3 = __toESM(require("node:fs"));
8750
- var import_node_path2 = __toESM(require("node:path"));
8985
+ var import_node_fs4 = __toESM(require("node:fs"));
8986
+ var import_node_path3 = __toESM(require("node:path"));
8751
8987
  var TWO_DAYS = 2 * 864e5;
8752
8988
  function isDir(p) {
8753
8989
  try {
8754
- return import_node_fs3.default.statSync(p).isDirectory();
8990
+ return import_node_fs4.default.statSync(p).isDirectory();
8755
8991
  } catch {
8756
8992
  return false;
8757
8993
  }
8758
8994
  }
8759
8995
  function isFile(p) {
8760
8996
  try {
8761
- return import_node_fs3.default.statSync(p).isFile();
8997
+ return import_node_fs4.default.statSync(p).isFile();
8762
8998
  } catch {
8763
8999
  return false;
8764
9000
  }
8765
9001
  }
8766
9002
  function listDirs(p) {
8767
9003
  try {
8768
- return import_node_fs3.default.readdirSync(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
9004
+ return import_node_fs4.default.readdirSync(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
8769
9005
  } catch {
8770
9006
  return [];
8771
9007
  }
@@ -8775,8 +9011,8 @@ function recentSubdirs(root, maxAgeMs = TWO_DAYS) {
8775
9011
  const now = Date.now();
8776
9012
  for (const name of listDirs(root)) {
8777
9013
  try {
8778
- const st = import_node_fs3.default.statSync(import_node_path2.default.join(root, name));
8779
- if (now - st.mtimeMs < maxAgeMs) out.push(import_node_path2.default.join(root, name));
9014
+ const st = import_node_fs4.default.statSync(import_node_path3.default.join(root, name));
9015
+ if (now - st.mtimeMs < maxAgeMs) out.push(import_node_path3.default.join(root, name));
8780
9016
  } catch {
8781
9017
  }
8782
9018
  }
@@ -8784,7 +9020,7 @@ function recentSubdirs(root, maxAgeMs = TWO_DAYS) {
8784
9020
  }
8785
9021
  function readJsonFile(p) {
8786
9022
  try {
8787
- return JSON.parse(import_node_fs3.default.readFileSync(p, "utf8"));
9023
+ return JSON.parse(import_node_fs4.default.readFileSync(p, "utf8"));
8788
9024
  } catch {
8789
9025
  return null;
8790
9026
  }
@@ -8793,7 +9029,7 @@ function makeRoot(dir, source, agent, format, kind, origin, exts, maxDepth = 6,
8793
9029
  return { dir, source, agent, format, kind, origin, exts, maxDepth, text };
8794
9030
  }
8795
9031
  function basenameNoExt(p) {
8796
- return import_node_path2.default.basename(p).replace(/\.[^.]+$/, "");
9032
+ return import_node_path3.default.basename(p).replace(/\.[^.]+$/, "");
8797
9033
  }
8798
9034
  function projectNameOf(cwd) {
8799
9035
  if (!cwd) return null;
@@ -8830,11 +9066,11 @@ function mergeSessions(parts) {
8830
9066
 
8831
9067
  // src/core/sources/codex.ts
8832
9068
  function codexHome() {
8833
- return process.env.CODEX_HOME || import_node_path3.default.join(import_node_os3.default.homedir(), ".codex");
9069
+ return process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex");
8834
9070
  }
8835
9071
  function underHome(h, envVar, ...rel) {
8836
- if (envVar && h.home === import_node_os3.default.homedir()) return envVar;
8837
- return import_node_path3.default.join(h.home, ...rel);
9072
+ if (envVar && h.home === import_node_os4.default.homedir()) return envVar;
9073
+ return import_node_path4.default.join(h.home, ...rel);
8838
9074
  }
8839
9075
  function dateDirs(root) {
8840
9076
  const dirs = [];
@@ -8842,10 +9078,10 @@ function dateDirs(root) {
8842
9078
  for (const offset of [0, -1]) {
8843
9079
  const d = /* @__PURE__ */ new Date();
8844
9080
  d.setDate(d.getDate() + offset);
8845
- dirs.push(import_node_path3.default.join(root, String(d.getFullYear()), pad(d.getMonth() + 1), pad(d.getDate())));
9081
+ dirs.push(import_node_path4.default.join(root, String(d.getFullYear()), pad(d.getMonth() + 1), pad(d.getDate())));
8846
9082
  }
8847
9083
  const u = /* @__PURE__ */ new Date();
8848
- dirs.push(import_node_path3.default.join(root, String(u.getUTCFullYear()), pad(u.getUTCMonth() + 1), pad(u.getUTCDate())));
9084
+ dirs.push(import_node_path4.default.join(root, String(u.getUTCFullYear()), pad(u.getUTCMonth() + 1), pad(u.getUTCDate())));
8849
9085
  return [...new Set(dirs)];
8850
9086
  }
8851
9087
  var codexSource = {
@@ -8856,8 +9092,8 @@ var codexSource = {
8856
9092
  const roots = [];
8857
9093
  for (const h of ctx.homes) {
8858
9094
  const home = underHome(h, process.env.CODEX_HOME, ".codex");
8859
- const sessions = import_node_path3.default.join(home, "sessions");
8860
- const archived = import_node_path3.default.join(home, "archived_sessions");
9095
+ const sessions = import_node_path4.default.join(home, "sessions");
9096
+ const archived = import_node_path4.default.join(home, "archived_sessions");
8861
9097
  if (isDir(sessions)) roots.push(makeRoot(sessions, "codex", "codex", "codex", "sessions", h.origin, [".jsonl"]));
8862
9098
  if (isDir(archived)) roots.push(makeRoot(archived, "codex", "codex", "codex", "archived", h.origin, [".jsonl"], 1));
8863
9099
  }
@@ -8878,12 +9114,12 @@ var codexSource = {
8878
9114
  };
8879
9115
 
8880
9116
  // src/core/sources/pi.ts
8881
- var import_node_os4 = __toESM(require("node:os"));
8882
- var import_node_path4 = __toESM(require("node:path"));
9117
+ var import_node_os5 = __toESM(require("node:os"));
9118
+ var import_node_path5 = __toESM(require("node:path"));
8883
9119
  init_src();
8884
9120
  function underHome2(h, envVar, ...rel) {
8885
- if (envVar && h.home === import_node_os4.default.homedir()) return envVar;
8886
- return import_node_path4.default.join(h.home, ...rel);
9121
+ if (envVar && h.home === import_node_os5.default.homedir()) return envVar;
9122
+ return import_node_path5.default.join(h.home, ...rel);
8887
9123
  }
8888
9124
  var piSource = {
8889
9125
  id: "pi",
@@ -8892,7 +9128,7 @@ var piSource = {
8892
9128
  discover(ctx) {
8893
9129
  const roots = [];
8894
9130
  for (const h of ctx.homes) {
8895
- const dir = import_node_path4.default.join(underHome2(h, process.env.PI_CODING_AGENT_DIR, ".pi", "agent"), "sessions");
9131
+ const dir = import_node_path5.default.join(underHome2(h, process.env.PI_CODING_AGENT_DIR, ".pi", "agent"), "sessions");
8896
9132
  if (isDir(dir)) roots.push(makeRoot(dir, "pi", "pi", "pi", "flat", h.origin, [".jsonl"], 3));
8897
9133
  }
8898
9134
  return roots;
@@ -8925,12 +9161,12 @@ var genericSource = {
8925
9161
  };
8926
9162
 
8927
9163
  // src/core/sources/hermes.ts
8928
- var import_node_os5 = __toESM(require("node:os"));
8929
- var import_node_path5 = __toESM(require("node:path"));
9164
+ var import_node_os6 = __toESM(require("node:os"));
9165
+ var import_node_path6 = __toESM(require("node:path"));
8930
9166
  init_src();
8931
9167
  function underHome3(h, envVar, ...rel) {
8932
- if (envVar && h.home === import_node_os5.default.homedir()) return envVar;
8933
- return import_node_path5.default.join(h.home, ...rel);
9168
+ if (envVar && h.home === import_node_os6.default.homedir()) return envVar;
9169
+ return import_node_path6.default.join(h.home, ...rel);
8934
9170
  }
8935
9171
  var INPUT_COLS = ["input_tokens", "prompt_tokens", "tokens_in", "input"];
8936
9172
  var OUTPUT_COLS = ["output_tokens", "completion_tokens", "tokens_out", "output"];
@@ -9056,9 +9292,9 @@ var hermesSource = {
9056
9292
  const roots = [];
9057
9293
  for (const h of ctx.homes) {
9058
9294
  const home = underHome3(h, process.env.HERMES_HOME, ".hermes");
9059
- const sessions = import_node_path5.default.join(home, "sessions");
9295
+ const sessions = import_node_path6.default.join(home, "sessions");
9060
9296
  if (isDir(sessions)) roots.push(makeRoot(sessions, "hermes", "hermes", "generic", "flat", h.origin, [".jsonl", ".json"], 4));
9061
- if (isFile(import_node_path5.default.join(home, "state.db"))) roots.push(makeRoot(home, "hermes", "hermes", "generic", "flat", h.origin, ["state.db"], 0, false));
9297
+ if (isFile(import_node_path6.default.join(home, "state.db"))) roots.push(makeRoot(home, "hermes", "hermes", "generic", "flat", h.origin, ["state.db"], 0, false));
9062
9298
  }
9063
9299
  return roots;
9064
9300
  },
@@ -9086,17 +9322,17 @@ var hermesSource = {
9086
9322
  };
9087
9323
 
9088
9324
  // src/core/sources/opencode.ts
9089
- var import_node_fs4 = __toESM(require("node:fs"));
9090
- var import_node_path6 = __toESM(require("node:path"));
9325
+ var import_node_fs5 = __toESM(require("node:fs"));
9326
+ var import_node_path7 = __toESM(require("node:path"));
9091
9327
  init_src();
9092
9328
  function opencodeDataDirs(h, env) {
9093
9329
  if (h.layout === "win32") {
9094
- const local = h.origin === "local" && env.LOCALAPPDATA ? env.LOCALAPPDATA : import_node_path6.default.join(h.home, "AppData", "Local");
9095
- const roaming = h.origin === "local" && env.APPDATA ? env.APPDATA : import_node_path6.default.join(h.home, "AppData", "Roaming");
9096
- return [import_node_path6.default.join(local, "opencode"), import_node_path6.default.join(roaming, "opencode")];
9330
+ const local = h.origin === "local" && env.LOCALAPPDATA ? env.LOCALAPPDATA : import_node_path7.default.join(h.home, "AppData", "Local");
9331
+ const roaming = h.origin === "local" && env.APPDATA ? env.APPDATA : import_node_path7.default.join(h.home, "AppData", "Roaming");
9332
+ return [import_node_path7.default.join(local, "opencode"), import_node_path7.default.join(roaming, "opencode")];
9097
9333
  }
9098
- const xdg = h.origin === "local" && env.XDG_DATA_HOME ? env.XDG_DATA_HOME : import_node_path6.default.join(h.home, ".local", "share");
9099
- return [import_node_path6.default.join(xdg, "opencode")];
9334
+ const xdg = h.origin === "local" && env.XDG_DATA_HOME ? env.XDG_DATA_HOME : import_node_path7.default.join(h.home, ".local", "share");
9335
+ return [import_node_path7.default.join(xdg, "opencode")];
9100
9336
  }
9101
9337
  var sessionCache = /* @__PURE__ */ new Map();
9102
9338
  var authCache = /* @__PURE__ */ new Map();
@@ -9104,27 +9340,27 @@ function findSessionInfo(storage, sessionID) {
9104
9340
  const key = `${storage}|${sessionID}`;
9105
9341
  const cached = sessionCache.get(key);
9106
9342
  if (cached && (cached.info || Date.now() - cached.at < 6e4)) return cached.info;
9107
- let info = null;
9108
- const sessionDir = import_node_path6.default.join(storage, "session");
9109
- const direct = import_node_path6.default.join(sessionDir, "info", `${sessionID}.json`);
9110
- if (import_node_fs4.default.existsSync(direct)) info = readJsonFile(direct);
9111
- if (!info) {
9343
+ let info2 = null;
9344
+ const sessionDir = import_node_path7.default.join(storage, "session");
9345
+ const direct = import_node_path7.default.join(sessionDir, "info", `${sessionID}.json`);
9346
+ if (import_node_fs5.default.existsSync(direct)) info2 = readJsonFile(direct);
9347
+ if (!info2) {
9112
9348
  for (const project of listDirs(sessionDir)) {
9113
- const p = import_node_path6.default.join(sessionDir, project, `${sessionID}.json`);
9114
- if (import_node_fs4.default.existsSync(p)) {
9115
- info = readJsonFile(p);
9349
+ const p = import_node_path7.default.join(sessionDir, project, `${sessionID}.json`);
9350
+ if (import_node_fs5.default.existsSync(p)) {
9351
+ info2 = readJsonFile(p);
9116
9352
  break;
9117
9353
  }
9118
9354
  }
9119
9355
  }
9120
- sessionCache.set(key, { at: Date.now(), info });
9121
- return info;
9356
+ sessionCache.set(key, { at: Date.now(), info: info2 });
9357
+ return info2;
9122
9358
  }
9123
9359
  function openaiIsOAuth(dataDir) {
9124
- const file = import_node_path6.default.join(dataDir, "auth.json");
9360
+ const file = import_node_path7.default.join(dataDir, "auth.json");
9125
9361
  let mtime = 0;
9126
9362
  try {
9127
- mtime = import_node_fs4.default.statSync(file).mtimeMs;
9363
+ mtime = import_node_fs5.default.statSync(file).mtimeMs;
9128
9364
  } catch {
9129
9365
  return false;
9130
9366
  }
@@ -9136,9 +9372,9 @@ function openaiIsOAuth(dataDir) {
9136
9372
  return oauth;
9137
9373
  }
9138
9374
  function storageOf(file) {
9139
- const parts = file.split(import_node_path6.default.sep);
9375
+ const parts = file.split(import_node_path7.default.sep);
9140
9376
  const idx = parts.lastIndexOf("storage");
9141
- return idx >= 0 ? parts.slice(0, idx + 1).join(import_node_path6.default.sep) : null;
9377
+ return idx >= 0 ? parts.slice(0, idx + 1).join(import_node_path7.default.sep) : null;
9142
9378
  }
9143
9379
  var opencodeSource = {
9144
9380
  id: "opencode",
@@ -9149,7 +9385,7 @@ var opencodeSource = {
9149
9385
  const roots = [];
9150
9386
  for (const h of ctx.homes) {
9151
9387
  for (const data of opencodeDataDirs(h, ctx.env)) {
9152
- const storage = import_node_path6.default.join(data, "storage");
9388
+ const storage = import_node_path7.default.join(data, "storage");
9153
9389
  if (isDir(storage)) roots.push(makeRoot(storage, "opencode", "opencode", "opencode", "flat", h.origin, [".json"], 5));
9154
9390
  }
9155
9391
  }
@@ -9157,7 +9393,7 @@ var opencodeSource = {
9157
9393
  },
9158
9394
  hotDirs(root) {
9159
9395
  const out = [root.dir];
9160
- for (const base of [import_node_path6.default.join(root.dir, "message"), import_node_path6.default.join(root.dir, "session", "message")]) out.push(...recentSubdirs(base));
9396
+ for (const base of [import_node_path7.default.join(root.dir, "message"), import_node_path7.default.join(root.dir, "session", "message")]) out.push(...recentSubdirs(base));
9161
9397
  return out;
9162
9398
  },
9163
9399
  watchRecursively: () => true,
@@ -9171,7 +9407,7 @@ var opencodeSource = {
9171
9407
  if (!m || m.role !== "assistant" || !m.tokens || typeof m.sessionID !== "string") return null;
9172
9408
  const provider = typeof m.providerID === "string" ? m.providerID : null;
9173
9409
  const storage = storageOf(file.path);
9174
- const dataDir = storage ? import_node_path6.default.dirname(storage) : null;
9410
+ const dataDir = storage ? import_node_path7.default.dirname(storage) : null;
9175
9411
  const codexAuth = isCodexAuthProvider(provider) || provider === "openai" && !!dataDir && openaiIsOAuth(dataDir);
9176
9412
  if (!opts.includeAllProviders && !codexAuth) return null;
9177
9413
  const t2 = m.tokens;
@@ -9188,16 +9424,16 @@ var opencodeSource = {
9188
9424
  provider,
9189
9425
  usage: { input, cached, cacheWrite, output, reasoning: t2.reasoning ?? 0, total: input + output, requests: 1 }
9190
9426
  };
9191
- const info = storage ? findSessionInfo(storage, m.sessionID) : null;
9192
- const cwd = info?.directory ?? null;
9427
+ const info2 = storage ? findSessionInfo(storage, m.sessionID) : null;
9428
+ const cwd = info2?.directory ?? null;
9193
9429
  return {
9194
9430
  sessionId: m.sessionID,
9195
9431
  agent: file.root.agent,
9196
9432
  provider,
9197
- startedAt: info?.time?.created ?? ts2,
9433
+ startedAt: info2?.time?.created ?? ts2,
9198
9434
  lastActivityAt: ts2,
9199
9435
  cwd,
9200
- projectName: projectNameOf(cwd) ?? info?.title ?? null,
9436
+ projectName: projectNameOf(cwd) ?? info2?.title ?? null,
9201
9437
  originator: "opencode",
9202
9438
  source: "opencode",
9203
9439
  cliVersion: null,
@@ -9214,22 +9450,22 @@ var opencodeSource = {
9214
9450
  };
9215
9451
 
9216
9452
  // src/core/sources/cline.ts
9217
- var import_node_path7 = __toESM(require("node:path"));
9453
+ var import_node_path8 = __toESM(require("node:path"));
9218
9454
  init_src();
9219
9455
  var APPS = ["Code", "Code - Insiders", "Cursor", "Windsurf", "VSCodium", "Trae"];
9220
9456
  function globalStorageDirs(h, env) {
9221
9457
  const out = [];
9222
- if (h.layout === "darwin") for (const app of APPS) out.push(import_node_path7.default.join(h.home, "Library", "Application Support", app, "User", "globalStorage"));
9458
+ if (h.layout === "darwin") for (const app of APPS) out.push(import_node_path8.default.join(h.home, "Library", "Application Support", app, "User", "globalStorage"));
9223
9459
  else if (h.layout === "win32") {
9224
- const roaming = h.origin === "local" && env.APPDATA ? env.APPDATA : import_node_path7.default.join(h.home, "AppData", "Roaming");
9225
- for (const app of APPS) out.push(import_node_path7.default.join(roaming, app, "User", "globalStorage"));
9460
+ const roaming = h.origin === "local" && env.APPDATA ? env.APPDATA : import_node_path8.default.join(h.home, "AppData", "Roaming");
9461
+ for (const app of APPS) out.push(import_node_path8.default.join(roaming, app, "User", "globalStorage"));
9226
9462
  } else {
9227
- const cfg = h.origin === "local" && env.XDG_CONFIG_HOME ? env.XDG_CONFIG_HOME : import_node_path7.default.join(h.home, ".config");
9228
- for (const app of APPS) out.push(import_node_path7.default.join(cfg, app, "User", "globalStorage"));
9463
+ const cfg = h.origin === "local" && env.XDG_CONFIG_HOME ? env.XDG_CONFIG_HOME : import_node_path8.default.join(h.home, ".config");
9464
+ for (const app of APPS) out.push(import_node_path8.default.join(cfg, app, "User", "globalStorage"));
9229
9465
  }
9230
9466
  if (h.layout !== "win32") {
9231
9467
  for (const server of [".vscode-server", ".vscode-server-insiders", ".cursor-server", ".windsurf-server"]) {
9232
- out.push(import_node_path7.default.join(h.home, server, "data", "User", "globalStorage"));
9468
+ out.push(import_node_path8.default.join(h.home, server, "data", "User", "globalStorage"));
9233
9469
  }
9234
9470
  }
9235
9471
  return out;
@@ -9252,7 +9488,7 @@ function clineFamily(id, label, extensionId) {
9252
9488
  const roots = [];
9253
9489
  for (const h of ctx.homes) {
9254
9490
  for (const gs of globalStorageDirs(h, ctx.env)) {
9255
- const tasks = import_node_path7.default.join(gs, extensionId, "tasks");
9491
+ const tasks = import_node_path8.default.join(gs, extensionId, "tasks");
9256
9492
  if (isDir(tasks)) roots.push(makeRoot(tasks, id, id, "cline", "flat", h.origin, ["ui_messages.json"], 2));
9257
9493
  }
9258
9494
  }
@@ -9268,8 +9504,8 @@ function clineFamily(id, label, extensionId) {
9268
9504
  return null;
9269
9505
  }
9270
9506
  if (!Array.isArray(messages)) return null;
9271
- const taskDir = import_node_path7.default.dirname(file.path);
9272
- const meta = readJsonFile(import_node_path7.default.join(taskDir, "task_metadata.json")) ?? {};
9507
+ const taskDir = import_node_path8.default.dirname(file.path);
9508
+ const meta = readJsonFile(import_node_path8.default.join(taskDir, "task_metadata.json")) ?? {};
9273
9509
  const usageList = Array.isArray(meta.model_usage) ? meta.model_usage : [];
9274
9510
  const agent = file.root.agent;
9275
9511
  const events = [];
@@ -9321,7 +9557,7 @@ function clineFamily(id, label, extensionId) {
9321
9557
  }
9322
9558
  const cwd = meta.cwd ?? meta.cwd_on_task_initialization ?? meta.workspace ?? null;
9323
9559
  return {
9324
- sessionId: import_node_path7.default.basename(taskDir),
9560
+ sessionId: import_node_path8.default.basename(taskDir),
9325
9561
  agent,
9326
9562
  provider,
9327
9563
  startedAt: startedAt ?? lastActivityAt,
@@ -9364,7 +9600,7 @@ function sourceFor(root) {
9364
9600
  function wslDistros() {
9365
9601
  if (process.platform !== "win32") return [];
9366
9602
  try {
9367
- const raw = (0, import_node_child_process2.execFileSync)("wsl.exe", ["-l", "-q"], { timeout: 5e3, windowsHide: true });
9603
+ const raw = (0, import_node_child_process3.execFileSync)("wsl.exe", ["-l", "-q"], { timeout: 5e3, windowsHide: true });
9368
9604
  return raw.toString("utf16le").split(String.fromCharCode(0)).join("").split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
9369
9605
  } catch {
9370
9606
  return [];
@@ -9374,22 +9610,22 @@ var SKIP_WINDOWS_USERS = /* @__PURE__ */ new Set(["public", "default", "default
9374
9610
  function userHomes() {
9375
9611
  const kind = platformKind();
9376
9612
  const localLayout = kind === "darwin" ? "darwin" : kind === "win32" ? "win32" : "linux";
9377
- const out = [{ home: import_node_os6.default.homedir(), origin: "local", layout: localLayout }];
9613
+ const out = [{ home: import_node_os7.default.homedir(), origin: "local", layout: localLayout }];
9378
9614
  if (kind === "win32") {
9379
9615
  for (const distro of wslDistros()) {
9380
9616
  for (const prefix of [`\\\\wsl$\\${distro}`, `\\\\wsl.localhost\\${distro}`]) {
9381
- const home = import_node_path8.default.join(prefix, "home");
9617
+ const home = import_node_path9.default.join(prefix, "home");
9382
9618
  const users = listDirs(home);
9383
9619
  if (!users.length) continue;
9384
- for (const u of users) out.push({ home: import_node_path8.default.join(home, u), origin: "wsl", layout: "linux" });
9385
- out.push({ home: import_node_path8.default.join(prefix, "root"), origin: "wsl", layout: "linux" });
9620
+ for (const u of users) out.push({ home: import_node_path9.default.join(home, u), origin: "wsl", layout: "linux" });
9621
+ out.push({ home: import_node_path9.default.join(prefix, "root"), origin: "wsl", layout: "linux" });
9386
9622
  break;
9387
9623
  }
9388
9624
  }
9389
9625
  } else if (kind === "wsl") {
9390
9626
  for (const u of listDirs("/mnt/c/Users")) {
9391
9627
  if (SKIP_WINDOWS_USERS.has(u.toLowerCase())) continue;
9392
- out.push({ home: import_node_path8.default.join("/mnt/c/Users", u), origin: "windows", layout: "win32" });
9628
+ out.push({ home: import_node_path9.default.join("/mnt/c/Users", u), origin: "windows", layout: "win32" });
9393
9629
  }
9394
9630
  }
9395
9631
  return out;
@@ -9400,7 +9636,7 @@ function discoverSessionRoots(opts = {}) {
9400
9636
  const roots = [];
9401
9637
  const seen = /* @__PURE__ */ new Set();
9402
9638
  const add = (r) => {
9403
- const key = `${import_node_path8.default.resolve(r.dir)}|${r.exts.join(",")}`;
9639
+ const key = `${import_node_path9.default.resolve(r.dir)}|${r.exts.join(",")}`;
9404
9640
  if (seen.has(key)) return;
9405
9641
  seen.add(key);
9406
9642
  roots.push(r);
@@ -9415,8 +9651,8 @@ function discoverSessionRoots(opts = {}) {
9415
9651
  for (const entry of opts.extraSessionDirs ?? []) {
9416
9652
  const e = normalizeExtraDir(entry);
9417
9653
  if (!e) continue;
9418
- const dir = e.path.replace(/^~(?=$|\/|\\)/, import_node_os6.default.homedir());
9419
- if (!import_node_fs5.default.existsSync(dir)) continue;
9654
+ const dir = e.path.replace(/^~(?=$|\/|\\)/, import_node_os7.default.homedir());
9655
+ if (!import_node_fs6.default.existsSync(dir)) continue;
9420
9656
  const def = byFormat[e.format ?? "generic"] ?? genericSource;
9421
9657
  add(def.extraRoot(dir, e.agent));
9422
9658
  }
@@ -9424,28 +9660,28 @@ function discoverSessionRoots(opts = {}) {
9424
9660
  }
9425
9661
  function walkFiles(root) {
9426
9662
  const out = [];
9427
- const walk = (dir, depth) => {
9663
+ const walk2 = (dir, depth) => {
9428
9664
  let entries;
9429
9665
  try {
9430
- entries = import_node_fs5.default.readdirSync(dir, { withFileTypes: true });
9666
+ entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
9431
9667
  } catch {
9432
9668
  return;
9433
9669
  }
9434
9670
  for (const e of entries) {
9435
- const p = import_node_path8.default.join(dir, e.name);
9671
+ const p = import_node_path9.default.join(dir, e.name);
9436
9672
  if (e.isDirectory()) {
9437
- if (depth < root.maxDepth && !e.name.startsWith(".")) walk(p, depth + 1);
9673
+ if (depth < root.maxDepth && !e.name.startsWith(".")) walk2(p, depth + 1);
9438
9674
  } else if (e.isFile() && root.exts.some((x) => e.name.endsWith(x))) {
9439
9675
  out.push(p);
9440
9676
  }
9441
9677
  }
9442
9678
  };
9443
- walk(root.dir, 0);
9679
+ walk2(root.dir, 0);
9444
9680
  return out;
9445
9681
  }
9446
9682
  function listFiles(dir, exts) {
9447
9683
  try {
9448
- return import_node_fs5.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && exts.some((x) => e.name.endsWith(x))).map((e) => import_node_path8.default.join(dir, e.name));
9684
+ return import_node_fs6.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && exts.some((x) => e.name.endsWith(x))).map((e) => import_node_path9.default.join(dir, e.name));
9449
9685
  } catch {
9450
9686
  return [];
9451
9687
  }
@@ -9509,7 +9745,7 @@ var SessionStore = class {
9509
9745
  async syncFile(p, root) {
9510
9746
  let st;
9511
9747
  try {
9512
- st = await import_node_fs6.default.promises.stat(p);
9748
+ st = await import_node_fs7.default.promises.stat(p);
9513
9749
  } catch {
9514
9750
  if (this.files.delete(p)) return true;
9515
9751
  return false;
@@ -9519,7 +9755,7 @@ var SessionStore = class {
9519
9755
  let session = null;
9520
9756
  if (st.size <= MAX_FILE_BYTES) {
9521
9757
  try {
9522
- const text = root.text ? await import_node_fs6.default.promises.readFile(p, "utf8") : "";
9758
+ const text = root.text ? await import_node_fs7.default.promises.readFile(p, "utf8") : "";
9523
9759
  session = sourceFor(root).parse({ path: p, text, root }, { includeAllProviders: this.getOptions().trackAllProviders });
9524
9760
  } catch {
9525
9761
  session = null;
@@ -9604,8 +9840,8 @@ var SessionStore = class {
9604
9840
  for (const [dir, recursive] of wanted) {
9605
9841
  if (this.watchers.has(dir)) continue;
9606
9842
  try {
9607
- if (!import_node_fs6.default.existsSync(dir)) continue;
9608
- const w = import_node_fs6.default.watch(dir, { persistent: false, recursive }, () => this.scheduleChange());
9843
+ if (!import_node_fs7.default.existsSync(dir)) continue;
9844
+ const w = import_node_fs7.default.watch(dir, { persistent: false, recursive }, () => this.scheduleChange());
9609
9845
  w.on("error", () => {
9610
9846
  w.close();
9611
9847
  this.watchers.delete(dir);
@@ -9630,7 +9866,20 @@ var SessionStore = class {
9630
9866
  // src/core/stats.ts
9631
9867
  init_src();
9632
9868
  var LIVE_WINDOW_MS = 5 * 60 * 1e3;
9869
+ var RATE_WINDOW_MS = 6e4;
9870
+ var BURST_WINDOW_MS = 1e4;
9633
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
+ }
9634
9883
  var PriceCache = class {
9635
9884
  constructor(overrides) {
9636
9885
  this.overrides = overrides;
@@ -9707,7 +9956,9 @@ function agentStats(sessions, since, prices) {
9707
9956
  function computeStats(input) {
9708
9957
  const now = input.now ?? Date.now();
9709
9958
  const prices = new PriceCache(input.pricing);
9710
- 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
+ );
9711
9962
  const allEvents = [];
9712
9963
  const sessionCosts = /* @__PURE__ */ new Map();
9713
9964
  let lastActivityAt = null;
@@ -9733,14 +9984,8 @@ function computeStats(input) {
9733
9984
  let live = null;
9734
9985
  const liveSession = sessions.filter((s) => s.events.length && now - s.lastActivityAt <= LIVE_WINDOW_MS).sort((a, b) => b.lastActivityAt - a.lastActivityAt)[0];
9735
9986
  if (liveSession) {
9736
- let t60 = 0;
9737
- let t10 = 0;
9738
- for (const e of liveSession.events) {
9739
- const age = now - e.ts;
9740
- if (age <= 6e4) t60 += e.usage.total;
9741
- if (age <= 1e4) t10 += e.usage.total;
9742
- }
9743
9987
  const last = liveSession.events[liveSession.events.length - 1];
9988
+ const start = liveSession.startedAt || liveSession.events[0].ts;
9744
9989
  live = {
9745
9990
  sessionId: liveSession.sessionId,
9746
9991
  agent: liveSession.agent,
@@ -9748,8 +9993,8 @@ function computeStats(input) {
9748
9993
  model: liveSession.model,
9749
9994
  startedAt: liveSession.startedAt,
9750
9995
  lastEventAt: last.ts,
9751
- tokensPerSecond: t60 / 60,
9752
- tokensPerSecond10s: t10 / 10,
9996
+ tokensPerSecond: outputRate(liveSession.events, now, RATE_WINDOW_MS, start),
9997
+ tokensPerSecond10s: outputRate(liveSession.events, now, BURST_WINDOW_MS, start),
9753
9998
  contextUsed: last.usage.input + last.usage.output,
9754
9999
  contextWindow: liveSession.contextWindow,
9755
10000
  sessionUsage: { ...liveSession.cumulative },
@@ -9776,6 +10021,7 @@ function computeStats(input) {
9776
10021
  }
9777
10022
  return {
9778
10023
  buckets,
10024
+ sessions: sessions.filter((s) => s.events.length),
9779
10025
  today,
9780
10026
  week,
9781
10027
  month,
@@ -9798,14 +10044,14 @@ init_auth();
9798
10044
  init_platform();
9799
10045
 
9800
10046
  // src/core/usage-api.ts
9801
- var import_node_fs7 = __toESM(require("node:fs"));
9802
- var import_node_path9 = __toESM(require("node:path"));
10047
+ var import_node_fs8 = __toESM(require("node:fs"));
10048
+ var import_node_path10 = __toESM(require("node:path"));
9803
10049
  init_src();
9804
10050
  function readCodexAuth() {
9805
- const file = import_node_path9.default.join(codexHome(), "auth.json");
10051
+ const file = import_node_path10.default.join(codexHome(), "auth.json");
9806
10052
  let raw;
9807
10053
  try {
9808
- raw = import_node_fs7.default.readFileSync(file, "utf8");
10054
+ raw = import_node_fs8.default.readFileSync(file, "utf8");
9809
10055
  } catch {
9810
10056
  return { error: `Codex login not found (${file})` };
9811
10057
  }
@@ -9856,8 +10102,173 @@ async function fetchLiveRateLimits(appVersion, timeoutMs = 1e4) {
9856
10102
  // src/core/engine.ts
9857
10103
  init_i18n();
9858
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
+
9859
10111
  // src/version.ts
9860
- var APP_VERSION = true ? "0.1.0" : "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
+ }
9861
10272
 
9862
10273
  // src/core/engine.ts
9863
10274
  var SHALLOW_MS = 3e3;
@@ -9866,6 +10277,7 @@ var TICK_MS = 2e3;
9866
10277
  var REMOTE_MS = 6e4;
9867
10278
  var LIVE_LIMITS_DEBOUNCE_MS = 1e4;
9868
10279
  var LIVE_LIMITS_MIN_GAP_MS = 2e4;
10280
+ var UPDATE_CHECK_MS = 6 * 60 * 60 * 1e3;
9869
10281
  var Engine = class extends import_node_events.EventEmitter {
9870
10282
  constructor(opts) {
9871
10283
  super();
@@ -9902,6 +10314,9 @@ var Engine = class extends import_node_events.EventEmitter {
9902
10314
  liveLimitsInFlight = false;
9903
10315
  liveLimitsTimer = null;
9904
10316
  lastLiveLimitsAttempt = 0;
10317
+ update = null;
10318
+ updateStatus = "idle";
10319
+ updateLog = null;
9905
10320
  get heatmapWeeks() {
9906
10321
  return this.opts.heatmapWeeks ?? 16;
9907
10322
  }
@@ -9923,6 +10338,8 @@ var Engine = class extends import_node_events.EventEmitter {
9923
10338
  return;
9924
10339
  }
9925
10340
  void this.refreshLiveLimits(true);
10341
+ void this.checkUpdate(false);
10342
+ this.timers.push(setInterval(() => void this.checkUpdate(false), UPDATE_CHECK_MS));
9926
10343
  this.store.startWatching(() => void this.refresh(false));
9927
10344
  this.timers.push(setInterval(() => void this.refresh(false), SHALLOW_MS));
9928
10345
  this.timers.push(setInterval(() => void this.refresh(true), DEEP_MS));
@@ -10029,7 +10446,7 @@ var Engine = class extends import_node_events.EventEmitter {
10029
10446
  if (!this.signedIn) return { buckets: 0, sessions: 0 };
10030
10447
  if (!this.stats) this.recompute();
10031
10448
  try {
10032
- 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);
10033
10450
  this.emitSnapshot();
10034
10451
  return r;
10035
10452
  } catch (err) {
@@ -10053,6 +10470,48 @@ var Engine = class extends import_node_events.EventEmitter {
10053
10470
  await this.uploader.fetchRemote(this.heatmapWeeks);
10054
10471
  this.recompute();
10055
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
+ }
10056
10515
  /** Start the device-code login flow (resolves when approved / denied / expired / cancelled). */
10057
10516
  async login(openBrowser, onCode) {
10058
10517
  if (this.pending) return { status: "cancelled" };
@@ -10145,6 +10604,7 @@ var Engine = class extends import_node_events.EventEmitter {
10145
10604
  rateLimits: this.liveLimits ?? fromLogRateLimits(s.logRateLimits),
10146
10605
  rateLimitsError: this.config.liveRateLimits ? this.liveLimitsError : null,
10147
10606
  rateLimitsUpdatedAt: this.liveLimits ? this.liveLimitsAt : s.logRateLimits?.observedAt ?? null,
10607
+ update: this.updateState(),
10148
10608
  modelsToday: s.modelsToday,
10149
10609
  modelsMonth: s.modelsMonth,
10150
10610
  byAgentToday: s.byAgentToday,
@@ -10203,23 +10663,60 @@ function applyDashboardFlag(flags) {
10203
10663
  function electronBinary() {
10204
10664
  try {
10205
10665
  const p = require("electron");
10206
- return typeof p === "string" && p ? p : null;
10666
+ return typeof p === "string" && p && import_node_fs10.default.existsSync(p) ? p : null;
10207
10667
  } catch {
10208
10668
  return null;
10209
10669
  }
10210
10670
  }
10211
- function startMenubar(background) {
10671
+ function electronPackageDir() {
10672
+ try {
10673
+ return import_node_path12.default.dirname(require.resolve("electron/package.json"));
10674
+ } catch {
10675
+ return null;
10676
+ }
10677
+ }
10678
+ async function ensureElectron() {
10679
+ const existing = electronBinary();
10680
+ if (existing) return existing;
10681
+ const dir = electronPackageDir();
10682
+ if (!dir) return null;
10683
+ const t2 = makeT(lang());
10684
+ console.log(t2("cliDownloadingElectron"));
10685
+ const log2 = { info: (m) => console.log(m), error: (m) => console.error(m) };
10686
+ let bin = null;
10687
+ try {
10688
+ bin = await installElectronBinary(dir, log2);
10689
+ } catch (err) {
10690
+ log2.error(` ${err.message}`);
10691
+ }
10692
+ if (!bin) {
10693
+ const installer = import_node_path12.default.join(dir, "install.js");
10694
+ if (import_node_fs10.default.existsSync(installer)) {
10695
+ const env = { ...process.env };
10696
+ delete env.ELECTRON_SKIP_BINARY_DOWNLOAD;
10697
+ (0, import_node_child_process5.spawnSync)(process.execPath, [installer], { stdio: "inherit", cwd: dir, env });
10698
+ }
10699
+ }
10700
+ try {
10701
+ delete require.cache[require.resolve("electron")];
10702
+ } catch {
10703
+ }
10704
+ const found = electronBinary();
10705
+ if (!found) console.error(t2("cliElectronDownloadFailed"));
10706
+ return found;
10707
+ }
10708
+ async function startMenubar(background) {
10212
10709
  const t2 = makeT(lang());
10213
- const bin = electronBinary();
10710
+ const bin = await ensureElectron();
10214
10711
  if (!bin) {
10215
10712
  console.error(t2("cliNoElectron"));
10216
10713
  return 2;
10217
10714
  }
10218
10715
  const env = { ...process.env, CODEX_TRACKER_HOME: configDir() };
10219
10716
  delete env.ELECTRON_RUN_AS_NODE;
10220
- const mainPath = import_node_path10.default.join(__dirname, "main.js");
10717
+ const mainPath = import_node_path12.default.join(__dirname, "main.js");
10221
10718
  console.log(t2("cliStartingMenubar"));
10222
- const child = (0, import_node_child_process3.spawn)(bin, [mainPath], {
10719
+ const child = (0, import_node_child_process5.spawn)(bin, [mainPath], {
10223
10720
  stdio: background ? "ignore" : "inherit",
10224
10721
  detached: background,
10225
10722
  env,
@@ -10302,6 +10799,10 @@ async function runStatus(json) {
10302
10799
  line(t2("cliStatusDirs"), s.sessionDirs.join("\n" + " ".repeat(15)) || "-");
10303
10800
  const byAgent = Object.entries(s.counts.byAgent).sort((a, b) => b[1].sessions - a[1].sessions).map(([agent, c]) => `${agent} ${c.sessions}`).join(", ");
10304
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
+ }
10305
10806
  return 0;
10306
10807
  }
10307
10808
  async function runLogin(flags) {
@@ -10451,6 +10952,31 @@ function runConfig(positional) {
10451
10952
  console.log(t2("cliConfigDir", { dir: configDir() }));
10452
10953
  return 0;
10453
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
+ }
10454
10980
  function runLang(positional) {
10455
10981
  const value = positional[0];
10456
10982
  if (!value || !["en", "zh", "auto"].includes(value)) {
@@ -10479,7 +11005,7 @@ async function main() {
10479
11005
  console.log(t2("cliNoDisplay"));
10480
11006
  return runAgent(args.flags);
10481
11007
  }
10482
- if (!electronBinary()) {
11008
+ if (!await ensureElectron()) {
10483
11009
  console.log(t2("cliNoElectron"));
10484
11010
  return runAgent(args.flags);
10485
11011
  }
@@ -10508,6 +11034,9 @@ async function main() {
10508
11034
  return runConfig(args.positional);
10509
11035
  case "lang":
10510
11036
  return runLang(args.positional);
11037
+ case "update":
11038
+ case "upgrade":
11039
+ return runUpdateCommand(args.flags);
10511
11040
  default:
10512
11041
  console.error(t2("cliUnknownCommand", { command: args.command }));
10513
11042
  console.log(t2("cliUsage"));