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/CHANGELOG.md +58 -0
- package/README.md +38 -7
- package/dist/cli.js +412 -74
- package/dist/main.js +389 -81
- package/dist/preload.js +2 -0
- package/dist/renderer/renderer.js +127 -4
- package/dist/renderer/styles.css +30 -0
- package/package.json +17 -14
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 *
|
|
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.
|
|
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
|
|
128
|
-
|
|
129
|
-
"
|
|
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.
|
|
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
|
|
381
|
+
const info2 = payload.info;
|
|
343
382
|
let cumulative = null;
|
|
344
383
|
let last = null;
|
|
345
|
-
if (
|
|
346
|
-
cumulative = toUsage(
|
|
347
|
-
last = toUsage(
|
|
348
|
-
if (typeof
|
|
349
|
-
} else if (
|
|
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
|
-
|
|
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
|
|
3279
|
+
const path13 = pathParts.slice(0, -1).join("/");
|
|
3198
3280
|
const exportName = pathParts[pathParts.length - 1];
|
|
3199
3281
|
if (exportName === "default") {
|
|
3200
|
-
return
|
|
3282
|
+
return path13;
|
|
3201
3283
|
} else {
|
|
3202
|
-
return
|
|
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
|
|
3498
|
-
var
|
|
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 =
|
|
3597
|
+
dir = path13.resolve(dir || ".");
|
|
3516
3598
|
try {
|
|
3517
|
-
var name = runtimeRequire(
|
|
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(
|
|
3604
|
+
var release = getFirst(path13.join(dir, "build/Release"), matchBuild);
|
|
3523
3605
|
if (release) return release;
|
|
3524
|
-
var debug = getFirst(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
3635
|
+
if (winner) return path13.join(prebuilds, winner.file);
|
|
3554
3636
|
}
|
|
3555
3637
|
};
|
|
3556
3638
|
function readdirSync(dir) {
|
|
3557
3639
|
try {
|
|
3558
|
-
return
|
|
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] &&
|
|
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" &&
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
8727
|
-
var
|
|
8728
|
-
var
|
|
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
|
|
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))
|
|
9265
|
-
if (!
|
|
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
|
-
|
|
9351
|
+
info2 = readJsonFile(p);
|
|
9270
9352
|
break;
|
|
9271
9353
|
}
|
|
9272
9354
|
}
|
|
9273
9355
|
}
|
|
9274
|
-
sessionCache.set(key, { at: Date.now(), info });
|
|
9275
|
-
return
|
|
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
|
|
9346
|
-
const cwd =
|
|
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:
|
|
9433
|
+
startedAt: info2?.time?.created ?? ts2,
|
|
9352
9434
|
lastActivityAt: ts2,
|
|
9353
9435
|
cwd,
|
|
9354
|
-
projectName: projectNameOf(cwd) ??
|
|
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:
|
|
9906
|
-
tokensPerSecond10s:
|
|
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.
|
|
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.
|
|
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 &&
|
|
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
|
|
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 =
|
|
10388
|
-
if (
|
|
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,
|
|
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 =
|
|
10717
|
+
const mainPath = import_node_path12.default.join(__dirname, "main.js");
|
|
10412
10718
|
console.log(t2("cliStartingMenubar"));
|
|
10413
|
-
const child = (0,
|
|
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"));
|