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/main.js
CHANGED
|
@@ -24,10 +24,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
|
|
25
25
|
// src/main.ts
|
|
26
26
|
var import_electron = require("electron");
|
|
27
|
-
var
|
|
27
|
+
var import_node_fs9 = __toESM(require("node:fs"));
|
|
28
28
|
var import_node_os7 = __toESM(require("node:os"));
|
|
29
|
-
var
|
|
30
|
-
var
|
|
29
|
+
var import_node_path11 = __toESM(require("node:path"));
|
|
30
|
+
var import_node_child_process4 = require("node:child_process");
|
|
31
31
|
var import_menubar = require("menubar");
|
|
32
32
|
|
|
33
33
|
// ../shared/src/usage.ts
|
|
@@ -50,20 +50,53 @@ function cacheHitRate(u) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// ../shared/src/pricing.ts
|
|
53
|
+
var LONG_CONTEXT_THRESHOLD = 272e3;
|
|
54
|
+
var long = (threshold, input, cachedInput, output) => ({
|
|
55
|
+
threshold,
|
|
56
|
+
input,
|
|
57
|
+
cachedInput,
|
|
58
|
+
output
|
|
59
|
+
});
|
|
53
60
|
var DEFAULT_PRICING = {
|
|
54
|
-
// GPT-5.
|
|
61
|
+
// GPT-5.6 family
|
|
62
|
+
"gpt-5.6-sol": { input: 4, cachedInput: 0.4, output: 20, long: long(LONG_CONTEXT_THRESHOLD, 8, 0.8, 30) },
|
|
63
|
+
"gpt-5.6-sol-codex": { input: 4, cachedInput: 0.4, output: 20, long: long(LONG_CONTEXT_THRESHOLD, 8, 0.8, 30) },
|
|
64
|
+
"gpt-5.6-terra": { input: 2, cachedInput: 0.2, output: 12, long: long(LONG_CONTEXT_THRESHOLD, 4, 0.4, 18) },
|
|
65
|
+
"gpt-5.6-terra-codex": { input: 2, cachedInput: 0.2, output: 12, long: long(LONG_CONTEXT_THRESHOLD, 4, 0.4, 18) },
|
|
66
|
+
"gpt-5.6-luna": { input: 0.2, cachedInput: 0.02, output: 1.2, long: long(LONG_CONTEXT_THRESHOLD, 0.4, 0.04, 1.8) },
|
|
67
|
+
"gpt-5.6-cyber": { input: 12.5, cachedInput: 1.25, output: 75 },
|
|
68
|
+
// GPT-5.5 family
|
|
69
|
+
"gpt-5.5": { input: 5, cachedInput: 0.5, output: 30, long: long(LONG_CONTEXT_THRESHOLD, 10, 1, 45) },
|
|
70
|
+
"gpt-5.5-codex": { input: 5, cachedInput: 0.5, output: 30, long: long(LONG_CONTEXT_THRESHOLD, 10, 1, 45) },
|
|
71
|
+
"gpt-5.5-pro": { input: 30, cachedInput: 30, output: 180, long: long(LONG_CONTEXT_THRESHOLD, 60, 60, 270) },
|
|
72
|
+
"gpt-5.5-cyber": { input: 12.5, cachedInput: 1.25, output: 75 },
|
|
73
|
+
// GPT-5.4 family
|
|
74
|
+
"gpt-5.4": { input: 2.5, cachedInput: 0.25, output: 15, long: long(LONG_CONTEXT_THRESHOLD, 5, 0.5, 22.5) },
|
|
75
|
+
"gpt-5.4-codex": { input: 2.5, cachedInput: 0.25, output: 15, long: long(LONG_CONTEXT_THRESHOLD, 5, 0.5, 22.5) },
|
|
76
|
+
"gpt-5.4-mini": { input: 0.75, cachedInput: 0.075, output: 4.5 },
|
|
77
|
+
"gpt-5.4-nano": { input: 0.2, cachedInput: 0.02, output: 1.25 },
|
|
78
|
+
"gpt-5.4-pro": { input: 30, cachedInput: 30, output: 180, long: long(LONG_CONTEXT_THRESHOLD, 60, 60, 270) },
|
|
79
|
+
// GPT-5.3 family (Codex-only release)
|
|
80
|
+
"gpt-5.3": { input: 1.75, cachedInput: 0.175, output: 14 },
|
|
81
|
+
"gpt-5.3-codex": { input: 1.75, cachedInput: 0.175, output: 14 },
|
|
82
|
+
// GPT-5.2 family
|
|
83
|
+
"gpt-5.2": { input: 1.75, cachedInput: 0.175, output: 14 },
|
|
84
|
+
"gpt-5.2-codex": { input: 1.75, cachedInput: 0.175, output: 14 },
|
|
85
|
+
"gpt-5.2-pro": { input: 21, cachedInput: 21, output: 168 },
|
|
86
|
+
// GPT-5.1 family
|
|
87
|
+
"gpt-5.1": { input: 1.25, cachedInput: 0.125, output: 10 },
|
|
88
|
+
"gpt-5.1-codex": { input: 1.25, cachedInput: 0.125, output: 10 },
|
|
89
|
+
"gpt-5.1-codex-max": { input: 1.25, cachedInput: 0.125, output: 10 },
|
|
90
|
+
"gpt-5.1-codex-mini": { input: 0.25, cachedInput: 0.025, output: 2 },
|
|
91
|
+
// GPT-5 family
|
|
55
92
|
"gpt-5": { input: 1.25, cachedInput: 0.125, output: 10 },
|
|
56
93
|
"gpt-5-codex": { input: 1.25, cachedInput: 0.125, output: 10 },
|
|
57
94
|
"gpt-5-mini": { input: 0.25, cachedInput: 0.025, output: 2 },
|
|
58
95
|
"gpt-5-nano": { input: 0.05, cachedInput: 5e-3, output: 0.4 },
|
|
59
96
|
"gpt-5-pro": { input: 15, cachedInput: 15, output: 120 },
|
|
60
|
-
"gpt-5
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"gpt-5.1-codex-mini": { input: 0.25, cachedInput: 0.025, output: 2 },
|
|
64
|
-
"gpt-5.2": { input: 1.75, cachedInput: 0.175, output: 14 },
|
|
65
|
-
"gpt-5.2-codex": { input: 1.75, cachedInput: 0.175, output: 14 },
|
|
66
|
-
"gpt-5.2-pro": { input: 21, cachedInput: 21, output: 168 },
|
|
97
|
+
"gpt-5-search-api": { input: 1.25, cachedInput: 0.125, output: 10 },
|
|
98
|
+
// ChatGPT-tuned endpoint
|
|
99
|
+
"chat-latest": { input: 5, cachedInput: 0.5, output: 30 },
|
|
67
100
|
// GPT-4.1 / 4o family
|
|
68
101
|
"gpt-4.1": { input: 2, cachedInput: 0.5, output: 8 },
|
|
69
102
|
"gpt-4.1-mini": { input: 0.4, cachedInput: 0.1, output: 1.6 },
|
|
@@ -79,7 +112,7 @@ var DEFAULT_PRICING = {
|
|
|
79
112
|
"o4-mini": { input: 1.1, cachedInput: 0.275, output: 4.4 },
|
|
80
113
|
"codex-mini-latest": { input: 1.5, cachedInput: 0.375, output: 6 }
|
|
81
114
|
};
|
|
82
|
-
var FALLBACK_PRICE_KEY = "gpt-5.
|
|
115
|
+
var FALLBACK_PRICE_KEY = "gpt-5.3-codex";
|
|
83
116
|
var TIERS = ["mini", "nano", "pro"];
|
|
84
117
|
function normalizeModelName(model) {
|
|
85
118
|
let m = (model || "").trim().toLowerCase();
|
|
@@ -89,6 +122,11 @@ function normalizeModelName(model) {
|
|
|
89
122
|
m = m.replace(/-preview$/, "");
|
|
90
123
|
return m;
|
|
91
124
|
}
|
|
125
|
+
function isOpenAIModel(model) {
|
|
126
|
+
const m = normalizeModelName(model);
|
|
127
|
+
if (m === "unknown") return true;
|
|
128
|
+
return /^(gpt[-.]|chatgpt|chat-latest|o[1-9](?:[-.]|$)|codex|text-|davinci|babbage|ada|curie)/.test(m);
|
|
129
|
+
}
|
|
92
130
|
function tierOf(name) {
|
|
93
131
|
for (const t2 of TIERS) if (name.split("-").includes(t2)) return t2;
|
|
94
132
|
return null;
|
|
@@ -128,11 +166,12 @@ function resolvePrice(model, overrides) {
|
|
|
128
166
|
}
|
|
129
167
|
function computeCost(u, p) {
|
|
130
168
|
const input = u.input ?? 0;
|
|
169
|
+
const rate = p.long && input > p.long.threshold ? p.long : p;
|
|
131
170
|
const cached = Math.min(u.cached ?? 0, input);
|
|
132
171
|
const cacheWrite = Math.min(u.cacheWrite ?? 0, Math.max(0, input - cached));
|
|
133
172
|
const fresh = Math.max(0, input - cached - cacheWrite);
|
|
134
173
|
const output = u.output ?? 0;
|
|
135
|
-
const cost = fresh *
|
|
174
|
+
const cost = fresh * rate.input + cached * rate.cachedInput + cacheWrite * (p.cacheWrite ?? rate.input) + output * rate.output;
|
|
136
175
|
return cost / 1e6;
|
|
137
176
|
}
|
|
138
177
|
|
|
@@ -312,14 +351,14 @@ function createSessionParser(fallbackSessionId) {
|
|
|
312
351
|
if (ts2 > lastActivityAt) lastActivityAt = ts2;
|
|
313
352
|
}
|
|
314
353
|
function onTokenCount(ts2, payload) {
|
|
315
|
-
const
|
|
354
|
+
const info2 = payload.info;
|
|
316
355
|
let cumulative = null;
|
|
317
356
|
let last = null;
|
|
318
|
-
if (
|
|
319
|
-
cumulative = toUsage(
|
|
320
|
-
last = toUsage(
|
|
321
|
-
if (typeof
|
|
322
|
-
} else if (
|
|
357
|
+
if (info2 && typeof info2 === "object") {
|
|
358
|
+
cumulative = toUsage(info2.total_token_usage);
|
|
359
|
+
last = toUsage(info2.last_token_usage);
|
|
360
|
+
if (typeof info2.model_context_window === "number") contextWindow = info2.model_context_window;
|
|
361
|
+
} else if (info2 === void 0) {
|
|
323
362
|
cumulative = toUsage(payload);
|
|
324
363
|
}
|
|
325
364
|
const rl = payload.rate_limits;
|
|
@@ -1022,7 +1061,8 @@ var DEFAULT_CONFIG = {
|
|
|
1022
1061
|
sources: { ...DEFAULT_SOURCES },
|
|
1023
1062
|
trackAllProviders: false,
|
|
1024
1063
|
liveRateLimits: true,
|
|
1025
|
-
usageRefreshSec: 60
|
|
1064
|
+
usageRefreshSec: 60,
|
|
1065
|
+
checkUpdates: true
|
|
1026
1066
|
};
|
|
1027
1067
|
function configDir() {
|
|
1028
1068
|
return process.env.CODEX_TRACKER_HOME || import_node_path.default.join(import_node_os.default.homedir(), ".codex-tracker");
|
|
@@ -1076,6 +1116,7 @@ function loadConfig() {
|
|
|
1076
1116
|
cfg.sources = normalizeSources(stored.sources);
|
|
1077
1117
|
cfg.trackAllProviders = stored.trackAllProviders === true;
|
|
1078
1118
|
cfg.liveRateLimits = stored.liveRateLimits !== false;
|
|
1119
|
+
cfg.checkUpdates = stored.checkUpdates !== false;
|
|
1079
1120
|
if (!(cfg.usageRefreshSec >= 15)) cfg.usageRefreshSec = DEFAULT_CONFIG.usageRefreshSec;
|
|
1080
1121
|
if (!["auto", "en", "zh"].includes(cfg.language)) cfg.language = "auto";
|
|
1081
1122
|
if (!(cfg.uploadIntervalSec >= 10)) cfg.uploadIntervalSec = DEFAULT_CONFIG.uploadIntervalSec;
|
|
@@ -1215,8 +1256,20 @@ var en = {
|
|
|
1215
1256
|
never: "never",
|
|
1216
1257
|
local: "Local",
|
|
1217
1258
|
remote: "Other devices",
|
|
1259
|
+
// updates
|
|
1260
|
+
update: "Update",
|
|
1261
|
+
updateAvailable: "Update to v{version}",
|
|
1262
|
+
updateNewVersion: "Version {version} is available (you have v{current})",
|
|
1263
|
+
updateUpToDate: "Up to date",
|
|
1264
|
+
updateChecking: "Checking\u2026",
|
|
1265
|
+
updateInstalling: "Installing\u2026",
|
|
1266
|
+
updateInstalled: "Updated to v{version} \u2014 restart to apply",
|
|
1267
|
+
updateFailed: "Update failed \u2014 run this yourself:",
|
|
1268
|
+
updateCheckFailed: "Update check failed: {message}",
|
|
1269
|
+
checkForUpdates: "Check for updates",
|
|
1270
|
+
releaseNotes: "Release notes",
|
|
1218
1271
|
// CLI
|
|
1219
|
-
cliUsage: `Usage: codex-tracker [command] [options]
|
|
1272
|
+
cliUsage: `Usage: codex-token-tracker [command] [options] (alias: codex-tracker)
|
|
1220
1273
|
|
|
1221
1274
|
Commands:
|
|
1222
1275
|
(none) Start the menu bar app (falls back to agent mode without a display)
|
|
@@ -1227,10 +1280,12 @@ Commands:
|
|
|
1227
1280
|
logout Disconnect this device
|
|
1228
1281
|
status Print today's usage, live session and rate limits
|
|
1229
1282
|
paths Show detected session directories (Codex, pi, OpenCode, Cline/Roo/Kilo, Hermes, custom)
|
|
1283
|
+
update Install the newest published version (--check only reports)
|
|
1230
1284
|
config get|set Read or change settings (config set uploadIntervalSec 30, config set sources.pi false)
|
|
1231
1285
|
lang <en|zh|auto> Set the display language
|
|
1232
1286
|
|
|
1233
1287
|
Options:
|
|
1288
|
+
--check update: report the newest version without installing it
|
|
1234
1289
|
--dashboard <url> Dashboard URL (self-hosted)
|
|
1235
1290
|
--background Start the menu bar app detached and return
|
|
1236
1291
|
--version, -v Print version
|
|
@@ -1272,7 +1327,13 @@ Options:
|
|
|
1272
1327
|
cliConfigSet: "Set {key} = {value}",
|
|
1273
1328
|
cliConfigUnknownKey: "Unknown config key: {key}. Keys: {keys}",
|
|
1274
1329
|
cliLangSet: "Language set to {lang}",
|
|
1275
|
-
cliConfigDir: "Config: {dir}"
|
|
1330
|
+
cliConfigDir: "Config: {dir}",
|
|
1331
|
+
cliUpdateLatest: "codex-token-tracker {version} is the latest version.",
|
|
1332
|
+
cliUpdateAvailable: "Update available: {current} \u2192 {latest}",
|
|
1333
|
+
cliUpdateRunning: "Running: {command}",
|
|
1334
|
+
cliUpdateDone: "Updated to {version}. Restart the menu bar app (or rerun the CLI) to use it.",
|
|
1335
|
+
cliUpdateFailed: "Update failed (exit {code}). Run it yourself: {command}",
|
|
1336
|
+
cliUpdateCheckFailed: "Could not reach the npm registry: {message}"
|
|
1276
1337
|
};
|
|
1277
1338
|
|
|
1278
1339
|
// src/i18n/zh.ts
|
|
@@ -1346,7 +1407,18 @@ var zh = {
|
|
|
1346
1407
|
never: "\u4ECE\u672A",
|
|
1347
1408
|
local: "\u672C\u673A",
|
|
1348
1409
|
remote: "\u5176\u4ED6\u8BBE\u5907",
|
|
1349
|
-
|
|
1410
|
+
update: "\u66F4\u65B0",
|
|
1411
|
+
updateAvailable: "\u66F4\u65B0\u5230 v{version}",
|
|
1412
|
+
updateNewVersion: "\u6709\u65B0\u7248\u672C {version}\uFF08\u5F53\u524D v{current}\uFF09",
|
|
1413
|
+
updateUpToDate: "\u5DF2\u662F\u6700\u65B0\u7248\u672C",
|
|
1414
|
+
updateChecking: "\u68C0\u67E5\u4E2D\u2026",
|
|
1415
|
+
updateInstalling: "\u5B89\u88C5\u4E2D\u2026",
|
|
1416
|
+
updateInstalled: "\u5DF2\u66F4\u65B0\u5230 v{version} \u2014 \u91CD\u542F\u540E\u751F\u6548",
|
|
1417
|
+
updateFailed: "\u66F4\u65B0\u5931\u8D25 \u2014 \u8BF7\u624B\u52A8\u6267\u884C\uFF1A",
|
|
1418
|
+
updateCheckFailed: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF1A{message}",
|
|
1419
|
+
checkForUpdates: "\u68C0\u67E5\u66F4\u65B0",
|
|
1420
|
+
releaseNotes: "\u66F4\u65B0\u65E5\u5FD7",
|
|
1421
|
+
cliUsage: `\u7528\u6CD5\uFF1Acodex-token-tracker [\u547D\u4EE4] [\u9009\u9879] \uFF08\u522B\u540D\uFF1Acodex-tracker\uFF09
|
|
1350
1422
|
|
|
1351
1423
|
\u547D\u4EE4\uFF1A
|
|
1352
1424
|
(\u65E0) \u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\uFF08\u65E0\u663E\u793A\u73AF\u5883\u65F6\u81EA\u52A8\u5207\u6362\u4E3A agent \u6A21\u5F0F\uFF09
|
|
@@ -1357,10 +1429,12 @@ var zh = {
|
|
|
1357
1429
|
logout \u65AD\u5F00\u672C\u8BBE\u5907
|
|
1358
1430
|
status \u6253\u5370\u4ECA\u65E5\u7528\u91CF\u3001\u5B9E\u65F6\u4F1A\u8BDD\u4E0E\u9650\u989D
|
|
1359
1431
|
paths \u663E\u793A\u68C0\u6D4B\u5230\u7684\u4F1A\u8BDD\u76EE\u5F55\uFF08Codex\u3001pi\u3001OpenCode\u3001Cline/Roo/Kilo\u3001Hermes\u3001\u81EA\u5B9A\u4E49\uFF09
|
|
1432
|
+
update \u5B89\u88C5\u6700\u65B0\u53D1\u5E03\u7248\u672C\uFF08--check \u53EA\u68C0\u67E5\u4E0D\u5B89\u88C5\uFF09
|
|
1360
1433
|
config get|set \u8BFB\u53D6\u6216\u4FEE\u6539\u8BBE\u7F6E\uFF08config set uploadIntervalSec 30\uFF0Cconfig set sources.pi false\uFF09
|
|
1361
1434
|
lang <en|zh|auto> \u8BBE\u7F6E\u663E\u793A\u8BED\u8A00
|
|
1362
1435
|
|
|
1363
1436
|
\u9009\u9879\uFF1A
|
|
1437
|
+
--check update\uFF1A\u53EA\u62A5\u544A\u6700\u65B0\u7248\u672C\uFF0C\u4E0D\u6267\u884C\u5B89\u88C5
|
|
1364
1438
|
--dashboard <url> \u4EEA\u8868\u76D8\u5730\u5740\uFF08\u81EA\u6258\u7BA1\uFF09
|
|
1365
1439
|
--background \u4EE5\u540E\u53F0\u65B9\u5F0F\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u5E76\u7ACB\u5373\u8FD4\u56DE
|
|
1366
1440
|
--version, -v \u663E\u793A\u7248\u672C
|
|
@@ -1402,7 +1476,13 @@ var zh = {
|
|
|
1402
1476
|
cliConfigSet: "\u5DF2\u8BBE\u7F6E {key} = {value}",
|
|
1403
1477
|
cliConfigUnknownKey: "\u672A\u77E5\u914D\u7F6E\u9879\uFF1A{key}\u3002\u53EF\u7528\uFF1A{keys}",
|
|
1404
1478
|
cliLangSet: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A {lang}",
|
|
1405
|
-
cliConfigDir: "\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}"
|
|
1479
|
+
cliConfigDir: "\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}",
|
|
1480
|
+
cliUpdateLatest: "codex-token-tracker {version} \u5DF2\u662F\u6700\u65B0\u7248\u672C\u3002",
|
|
1481
|
+
cliUpdateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C\uFF1A{current} \u2192 {latest}",
|
|
1482
|
+
cliUpdateRunning: "\u6B63\u5728\u6267\u884C\uFF1A{command}",
|
|
1483
|
+
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",
|
|
1484
|
+
cliUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF1A{command}",
|
|
1485
|
+
cliUpdateCheckFailed: "\u65E0\u6CD5\u8BBF\u95EE npm registry\uFF1A{message}"
|
|
1406
1486
|
};
|
|
1407
1487
|
|
|
1408
1488
|
// src/i18n/index.ts
|
|
@@ -1849,21 +1929,21 @@ function findSessionInfo(storage, sessionID) {
|
|
|
1849
1929
|
const key = `${storage}|${sessionID}`;
|
|
1850
1930
|
const cached = sessionCache.get(key);
|
|
1851
1931
|
if (cached && (cached.info || Date.now() - cached.at < 6e4)) return cached.info;
|
|
1852
|
-
let
|
|
1932
|
+
let info2 = null;
|
|
1853
1933
|
const sessionDir = import_node_path6.default.join(storage, "session");
|
|
1854
1934
|
const direct = import_node_path6.default.join(sessionDir, "info", `${sessionID}.json`);
|
|
1855
|
-
if (import_node_fs4.default.existsSync(direct))
|
|
1856
|
-
if (!
|
|
1935
|
+
if (import_node_fs4.default.existsSync(direct)) info2 = readJsonFile(direct);
|
|
1936
|
+
if (!info2) {
|
|
1857
1937
|
for (const project of listDirs(sessionDir)) {
|
|
1858
1938
|
const p = import_node_path6.default.join(sessionDir, project, `${sessionID}.json`);
|
|
1859
1939
|
if (import_node_fs4.default.existsSync(p)) {
|
|
1860
|
-
|
|
1940
|
+
info2 = readJsonFile(p);
|
|
1861
1941
|
break;
|
|
1862
1942
|
}
|
|
1863
1943
|
}
|
|
1864
1944
|
}
|
|
1865
|
-
sessionCache.set(key, { at: Date.now(), info });
|
|
1866
|
-
return
|
|
1945
|
+
sessionCache.set(key, { at: Date.now(), info: info2 });
|
|
1946
|
+
return info2;
|
|
1867
1947
|
}
|
|
1868
1948
|
function openaiIsOAuth(dataDir) {
|
|
1869
1949
|
const file = import_node_path6.default.join(dataDir, "auth.json");
|
|
@@ -1933,16 +2013,16 @@ var opencodeSource = {
|
|
|
1933
2013
|
provider,
|
|
1934
2014
|
usage: { input, cached, cacheWrite, output, reasoning: t2.reasoning ?? 0, total: input + output, requests: 1 }
|
|
1935
2015
|
};
|
|
1936
|
-
const
|
|
1937
|
-
const cwd =
|
|
2016
|
+
const info2 = storage ? findSessionInfo(storage, m.sessionID) : null;
|
|
2017
|
+
const cwd = info2?.directory ?? null;
|
|
1938
2018
|
return {
|
|
1939
2019
|
sessionId: m.sessionID,
|
|
1940
2020
|
agent: file.root.agent,
|
|
1941
2021
|
provider,
|
|
1942
|
-
startedAt:
|
|
2022
|
+
startedAt: info2?.time?.created ?? ts2,
|
|
1943
2023
|
lastActivityAt: ts2,
|
|
1944
2024
|
cwd,
|
|
1945
|
-
projectName: projectNameOf(cwd) ??
|
|
2025
|
+
projectName: projectNameOf(cwd) ?? info2?.title ?? null,
|
|
1946
2026
|
originator: "opencode",
|
|
1947
2027
|
source: "opencode",
|
|
1948
2028
|
cliVersion: null,
|
|
@@ -2366,7 +2446,20 @@ var SessionStore = class {
|
|
|
2366
2446
|
|
|
2367
2447
|
// src/core/stats.ts
|
|
2368
2448
|
var LIVE_WINDOW_MS = 5 * 60 * 1e3;
|
|
2449
|
+
var RATE_WINDOW_MS = 6e4;
|
|
2450
|
+
var BURST_WINDOW_MS = 1e4;
|
|
2369
2451
|
var DAY3 = 864e5;
|
|
2452
|
+
function outputRate(events, now, windowMs, sessionStart) {
|
|
2453
|
+
const from = now - windowMs;
|
|
2454
|
+
let output = 0;
|
|
2455
|
+
for (const e of events) {
|
|
2456
|
+
if (e.ts <= from) continue;
|
|
2457
|
+
output += e.usage.output;
|
|
2458
|
+
}
|
|
2459
|
+
if (output <= 0) return 0;
|
|
2460
|
+
const elapsedSec = Math.max(1, (now - Math.max(from, sessionStart)) / 1e3);
|
|
2461
|
+
return output / elapsedSec;
|
|
2462
|
+
}
|
|
2370
2463
|
var PriceCache = class {
|
|
2371
2464
|
constructor(overrides) {
|
|
2372
2465
|
this.overrides = overrides;
|
|
@@ -2443,7 +2536,9 @@ function agentStats(sessions, since, prices) {
|
|
|
2443
2536
|
function computeStats(input) {
|
|
2444
2537
|
const now = input.now ?? Date.now();
|
|
2445
2538
|
const prices = new PriceCache(input.pricing);
|
|
2446
|
-
const sessions = input.sessions
|
|
2539
|
+
const sessions = input.sessions.map(
|
|
2540
|
+
(s) => s.events.every((e) => isOpenAIModel(e.model)) ? s : { ...s, events: s.events.filter((e) => isOpenAIModel(e.model)) }
|
|
2541
|
+
);
|
|
2447
2542
|
const allEvents = [];
|
|
2448
2543
|
const sessionCosts = /* @__PURE__ */ new Map();
|
|
2449
2544
|
let lastActivityAt = null;
|
|
@@ -2469,14 +2564,8 @@ function computeStats(input) {
|
|
|
2469
2564
|
let live = null;
|
|
2470
2565
|
const liveSession = sessions.filter((s) => s.events.length && now - s.lastActivityAt <= LIVE_WINDOW_MS).sort((a, b) => b.lastActivityAt - a.lastActivityAt)[0];
|
|
2471
2566
|
if (liveSession) {
|
|
2472
|
-
let t60 = 0;
|
|
2473
|
-
let t10 = 0;
|
|
2474
|
-
for (const e of liveSession.events) {
|
|
2475
|
-
const age = now - e.ts;
|
|
2476
|
-
if (age <= 6e4) t60 += e.usage.total;
|
|
2477
|
-
if (age <= 1e4) t10 += e.usage.total;
|
|
2478
|
-
}
|
|
2479
2567
|
const last = liveSession.events[liveSession.events.length - 1];
|
|
2568
|
+
const start = liveSession.startedAt || liveSession.events[0].ts;
|
|
2480
2569
|
live = {
|
|
2481
2570
|
sessionId: liveSession.sessionId,
|
|
2482
2571
|
agent: liveSession.agent,
|
|
@@ -2484,8 +2573,8 @@ function computeStats(input) {
|
|
|
2484
2573
|
model: liveSession.model,
|
|
2485
2574
|
startedAt: liveSession.startedAt,
|
|
2486
2575
|
lastEventAt: last.ts,
|
|
2487
|
-
tokensPerSecond:
|
|
2488
|
-
tokensPerSecond10s:
|
|
2576
|
+
tokensPerSecond: outputRate(liveSession.events, now, RATE_WINDOW_MS, start),
|
|
2577
|
+
tokensPerSecond10s: outputRate(liveSession.events, now, BURST_WINDOW_MS, start),
|
|
2489
2578
|
contextUsed: last.usage.input + last.usage.output,
|
|
2490
2579
|
contextWindow: liveSession.contextWindow,
|
|
2491
2580
|
sessionUsage: { ...liveSession.cumulative },
|
|
@@ -2512,6 +2601,7 @@ function computeStats(input) {
|
|
|
2512
2601
|
}
|
|
2513
2602
|
return {
|
|
2514
2603
|
buckets,
|
|
2604
|
+
sessions: sessions.filter((s) => s.events.length),
|
|
2515
2605
|
today,
|
|
2516
2606
|
week,
|
|
2517
2607
|
month,
|
|
@@ -3869,12 +3959,12 @@ function createApi(pathParts = []) {
|
|
|
3869
3959
|
`API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
|
|
3870
3960
|
);
|
|
3871
3961
|
}
|
|
3872
|
-
const
|
|
3962
|
+
const path12 = pathParts.slice(0, -1).join("/");
|
|
3873
3963
|
const exportName = pathParts[pathParts.length - 1];
|
|
3874
3964
|
if (exportName === "default") {
|
|
3875
|
-
return
|
|
3965
|
+
return path12;
|
|
3876
3966
|
} else {
|
|
3877
|
-
return
|
|
3967
|
+
return path12 + ":" + exportName;
|
|
3878
3968
|
}
|
|
3879
3969
|
} else if (prop === Symbol.toStringTag) {
|
|
3880
3970
|
return "FunctionReference";
|
|
@@ -4038,8 +4128,8 @@ var require_constants = __commonJS({
|
|
|
4038
4128
|
});
|
|
4039
4129
|
var require_node_gyp_build = __commonJS({
|
|
4040
4130
|
"../node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports2, module2) {
|
|
4041
|
-
var
|
|
4042
|
-
var
|
|
4131
|
+
var fs10 = __require("fs");
|
|
4132
|
+
var path12 = __require("path");
|
|
4043
4133
|
var os8 = __require("os");
|
|
4044
4134
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
4045
4135
|
var vars2 = process.config && process.config.variables || {};
|
|
@@ -4056,21 +4146,21 @@ var require_node_gyp_build = __commonJS({
|
|
|
4056
4146
|
return runtimeRequire(load.resolve(dir));
|
|
4057
4147
|
}
|
|
4058
4148
|
load.resolve = load.path = function(dir) {
|
|
4059
|
-
dir =
|
|
4149
|
+
dir = path12.resolve(dir || ".");
|
|
4060
4150
|
try {
|
|
4061
|
-
var name = runtimeRequire(
|
|
4151
|
+
var name = runtimeRequire(path12.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
4062
4152
|
if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
|
|
4063
4153
|
} catch (err) {
|
|
4064
4154
|
}
|
|
4065
4155
|
if (!prebuildsOnly) {
|
|
4066
|
-
var release = getFirst(
|
|
4156
|
+
var release = getFirst(path12.join(dir, "build/Release"), matchBuild);
|
|
4067
4157
|
if (release) return release;
|
|
4068
|
-
var debug = getFirst(
|
|
4158
|
+
var debug = getFirst(path12.join(dir, "build/Debug"), matchBuild);
|
|
4069
4159
|
if (debug) return debug;
|
|
4070
4160
|
}
|
|
4071
4161
|
var prebuild = resolve(dir);
|
|
4072
4162
|
if (prebuild) return prebuild;
|
|
4073
|
-
var nearby = resolve(
|
|
4163
|
+
var nearby = resolve(path12.dirname(process.execPath));
|
|
4074
4164
|
if (nearby) return nearby;
|
|
4075
4165
|
var target = [
|
|
4076
4166
|
"platform=" + platform,
|
|
@@ -4087,26 +4177,26 @@ var require_node_gyp_build = __commonJS({
|
|
|
4087
4177
|
].filter(Boolean).join(" ");
|
|
4088
4178
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
4089
4179
|
function resolve(dir2) {
|
|
4090
|
-
var tuples = readdirSync(
|
|
4180
|
+
var tuples = readdirSync(path12.join(dir2, "prebuilds")).map(parseTuple);
|
|
4091
4181
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
4092
4182
|
if (!tuple) return;
|
|
4093
|
-
var prebuilds =
|
|
4183
|
+
var prebuilds = path12.join(dir2, "prebuilds", tuple.name);
|
|
4094
4184
|
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
4095
4185
|
var candidates = parsed.filter(matchTags(runtime, abi));
|
|
4096
4186
|
var winner = candidates.sort(compareTags(runtime))[0];
|
|
4097
|
-
if (winner) return
|
|
4187
|
+
if (winner) return path12.join(prebuilds, winner.file);
|
|
4098
4188
|
}
|
|
4099
4189
|
};
|
|
4100
4190
|
function readdirSync(dir) {
|
|
4101
4191
|
try {
|
|
4102
|
-
return
|
|
4192
|
+
return fs10.readdirSync(dir);
|
|
4103
4193
|
} catch (err) {
|
|
4104
4194
|
return [];
|
|
4105
4195
|
}
|
|
4106
4196
|
}
|
|
4107
4197
|
function getFirst(dir, filter) {
|
|
4108
4198
|
var files = readdirSync(dir).filter(filter);
|
|
4109
|
-
return files[0] &&
|
|
4199
|
+
return files[0] && path12.join(dir, files[0]);
|
|
4110
4200
|
}
|
|
4111
4201
|
function matchBuild(name) {
|
|
4112
4202
|
return /\.node$/.test(name);
|
|
@@ -4193,7 +4283,7 @@ var require_node_gyp_build = __commonJS({
|
|
|
4193
4283
|
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
4194
4284
|
}
|
|
4195
4285
|
function isAlpine(platform2) {
|
|
4196
|
-
return platform2 === "linux" &&
|
|
4286
|
+
return platform2 === "linux" && fs10.existsSync("/etc/alpine-release");
|
|
4197
4287
|
}
|
|
4198
4288
|
load.parseTags = parseTags;
|
|
4199
4289
|
load.matchTags = matchTags;
|
|
@@ -7739,13 +7829,13 @@ var require_websocket_server = __commonJS({
|
|
|
7739
7829
|
}
|
|
7740
7830
|
}
|
|
7741
7831
|
if (this.options.verifyClient) {
|
|
7742
|
-
const
|
|
7832
|
+
const info2 = {
|
|
7743
7833
|
origin: req.headers[`${version2 === 8 ? "sec-websocket-origin" : "origin"}`],
|
|
7744
7834
|
secure: !!(req.socket.authorized || req.socket.encrypted),
|
|
7745
7835
|
req
|
|
7746
7836
|
};
|
|
7747
7837
|
if (this.options.verifyClient.length === 2) {
|
|
7748
|
-
this.options.verifyClient(
|
|
7838
|
+
this.options.verifyClient(info2, (verified, code2, message, headers) => {
|
|
7749
7839
|
if (!verified) {
|
|
7750
7840
|
return abortHandshake(socket, code2 || 401, message, headers);
|
|
7751
7841
|
}
|
|
@@ -7761,7 +7851,7 @@ var require_websocket_server = __commonJS({
|
|
|
7761
7851
|
});
|
|
7762
7852
|
return;
|
|
7763
7853
|
}
|
|
7764
|
-
if (!this.options.verifyClient(
|
|
7854
|
+
if (!this.options.verifyClient(info2)) return abortHandshake(socket, 401);
|
|
7765
7855
|
}
|
|
7766
7856
|
this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
|
|
7767
7857
|
}
|
|
@@ -8967,10 +9057,174 @@ async function fetchLiveRateLimits(appVersion, timeoutMs = 1e4) {
|
|
|
8967
9057
|
}
|
|
8968
9058
|
}
|
|
8969
9059
|
|
|
9060
|
+
// src/core/update.ts
|
|
9061
|
+
var import_node_fs8 = __toESM(require("node:fs"));
|
|
9062
|
+
var import_node_path10 = __toESM(require("node:path"));
|
|
9063
|
+
var import_node_child_process3 = require("node:child_process");
|
|
9064
|
+
|
|
8970
9065
|
// src/version.ts
|
|
8971
|
-
var APP_VERSION = true ? "0.
|
|
9066
|
+
var APP_VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
8972
9067
|
var APP_NAME = "Codex Tracker";
|
|
8973
9068
|
|
|
9069
|
+
// src/core/update.ts
|
|
9070
|
+
var NPM_PACKAGE = "codex-token-tracker";
|
|
9071
|
+
var CHECK_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
9072
|
+
var REQUEST_TIMEOUT_MS = 8e3;
|
|
9073
|
+
var INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
9074
|
+
var LOG_TAIL_CHARS = 4e3;
|
|
9075
|
+
function parseVersion(v2) {
|
|
9076
|
+
const s = String(v2 ?? "").trim().replace(/^v/, "");
|
|
9077
|
+
const i2 = s.indexOf("-");
|
|
9078
|
+
const core = i2 === -1 ? s : s.slice(0, i2);
|
|
9079
|
+
const pre = i2 === -1 ? "" : s.slice(i2 + 1);
|
|
9080
|
+
const n = core.split(".").map((x) => Number.parseInt(x, 10) || 0);
|
|
9081
|
+
return { nums: [n[0] ?? 0, n[1] ?? 0, n[2] ?? 0], pre };
|
|
9082
|
+
}
|
|
9083
|
+
function compareVersions(a, b) {
|
|
9084
|
+
const A = parseVersion(a);
|
|
9085
|
+
const B = parseVersion(b);
|
|
9086
|
+
for (let i2 = 0; i2 < 3; i2++) if (A.nums[i2] !== B.nums[i2]) return A.nums[i2] < B.nums[i2] ? -1 : 1;
|
|
9087
|
+
if (A.pre === B.pre) return 0;
|
|
9088
|
+
if (!A.pre) return 1;
|
|
9089
|
+
if (!B.pre) return -1;
|
|
9090
|
+
return A.pre < B.pre ? -1 : 1;
|
|
9091
|
+
}
|
|
9092
|
+
function isDevBuild(version2) {
|
|
9093
|
+
return version2.startsWith("0.0.0");
|
|
9094
|
+
}
|
|
9095
|
+
function registryBase() {
|
|
9096
|
+
const r = process.env.CODEX_TRACKER_REGISTRY || process.env.npm_config_registry || process.env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org";
|
|
9097
|
+
return r.replace(/\/+$/, "");
|
|
9098
|
+
}
|
|
9099
|
+
async function fetchLatestVersion(signal) {
|
|
9100
|
+
const ctrl = new AbortController();
|
|
9101
|
+
const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
|
|
9102
|
+
signal?.addEventListener("abort", () => ctrl.abort(), { once: true });
|
|
9103
|
+
try {
|
|
9104
|
+
const res = await fetch(`${registryBase()}/-/package/${NPM_PACKAGE}/dist-tags`, {
|
|
9105
|
+
signal: ctrl.signal,
|
|
9106
|
+
headers: { accept: "application/json" }
|
|
9107
|
+
});
|
|
9108
|
+
if (!res.ok) throw new Error(`registry responded ${res.status}`);
|
|
9109
|
+
const json = await res.json();
|
|
9110
|
+
const latest = json?.latest;
|
|
9111
|
+
if (typeof latest !== "string" || !latest) throw new Error("registry returned no `latest` tag");
|
|
9112
|
+
return latest;
|
|
9113
|
+
} finally {
|
|
9114
|
+
clearTimeout(timer);
|
|
9115
|
+
}
|
|
9116
|
+
}
|
|
9117
|
+
function detectPackageManager(installDir = import_node_path10.default.resolve(__dirname, "..")) {
|
|
9118
|
+
const p = installDir.replace(/\\/g, "/").toLowerCase();
|
|
9119
|
+
if (/\/\.?bun\//.test(p)) return "bun";
|
|
9120
|
+
if (/\/\.?pnpm[/-]/.test(p)) return "pnpm";
|
|
9121
|
+
if (/\/\.?yarn\//.test(p)) return "yarn";
|
|
9122
|
+
const ua = (process.env.npm_config_user_agent ?? "").toLowerCase();
|
|
9123
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
9124
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
9125
|
+
if (ua.startsWith("bun")) return "bun";
|
|
9126
|
+
return "npm";
|
|
9127
|
+
}
|
|
9128
|
+
function updateArgs(pm, spec = `${NPM_PACKAGE}@latest`) {
|
|
9129
|
+
switch (pm) {
|
|
9130
|
+
case "pnpm":
|
|
9131
|
+
return ["add", "-g", spec];
|
|
9132
|
+
case "yarn":
|
|
9133
|
+
return ["global", "add", spec];
|
|
9134
|
+
case "bun":
|
|
9135
|
+
return ["add", "-g", spec];
|
|
9136
|
+
default:
|
|
9137
|
+
return ["install", "-g", spec];
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
function updateCommand(pm, spec) {
|
|
9141
|
+
return `${pm} ${updateArgs(pm, spec).join(" ")}`;
|
|
9142
|
+
}
|
|
9143
|
+
function cachePath() {
|
|
9144
|
+
return import_node_path10.default.join(configDir(), "update.json");
|
|
9145
|
+
}
|
|
9146
|
+
function readCache() {
|
|
9147
|
+
try {
|
|
9148
|
+
const raw = JSON.parse(import_node_fs8.default.readFileSync(cachePath(), "utf8"));
|
|
9149
|
+
if (typeof raw?.latest === "string" && typeof raw.checkedAt === "number") {
|
|
9150
|
+
return { latest: raw.latest, checkedAt: raw.checkedAt };
|
|
9151
|
+
}
|
|
9152
|
+
} catch {
|
|
9153
|
+
}
|
|
9154
|
+
return null;
|
|
9155
|
+
}
|
|
9156
|
+
function writeCache(c) {
|
|
9157
|
+
try {
|
|
9158
|
+
import_node_fs8.default.mkdirSync(import_node_path10.default.dirname(cachePath()), { recursive: true });
|
|
9159
|
+
import_node_fs8.default.writeFileSync(cachePath(), JSON.stringify(c, null, 2));
|
|
9160
|
+
} catch {
|
|
9161
|
+
}
|
|
9162
|
+
}
|
|
9163
|
+
function info(current, latest, checkedAt, error) {
|
|
9164
|
+
const pm = detectPackageManager();
|
|
9165
|
+
return {
|
|
9166
|
+
current,
|
|
9167
|
+
latest,
|
|
9168
|
+
available: Boolean(latest) && !isDevBuild(current) && compareVersions(latest, current) > 0,
|
|
9169
|
+
checkedAt,
|
|
9170
|
+
error,
|
|
9171
|
+
packageManager: pm,
|
|
9172
|
+
command: updateCommand(pm)
|
|
9173
|
+
};
|
|
9174
|
+
}
|
|
9175
|
+
async function checkForUpdate(opts = {}) {
|
|
9176
|
+
const current = opts.current ?? APP_VERSION;
|
|
9177
|
+
const cached = readCache();
|
|
9178
|
+
if (!opts.force && cached && Date.now() - cached.checkedAt < CHECK_TTL_MS) {
|
|
9179
|
+
return info(current, cached.latest, cached.checkedAt, null);
|
|
9180
|
+
}
|
|
9181
|
+
try {
|
|
9182
|
+
const latest = await fetchLatestVersion(opts.signal);
|
|
9183
|
+
const checkedAt = Date.now();
|
|
9184
|
+
writeCache({ latest, checkedAt });
|
|
9185
|
+
return info(current, latest, checkedAt, null);
|
|
9186
|
+
} catch (err) {
|
|
9187
|
+
return info(current, cached?.latest ?? null, cached?.checkedAt ?? null, err.message);
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
function runUpdate(opts = {}) {
|
|
9191
|
+
const pm = detectPackageManager();
|
|
9192
|
+
const spec = opts.version ? `${NPM_PACKAGE}@${opts.version}` : `${NPM_PACKAGE}@latest`;
|
|
9193
|
+
const args = updateArgs(pm, spec);
|
|
9194
|
+
const command = `${pm} ${args.join(" ")}`;
|
|
9195
|
+
return new Promise((resolve) => {
|
|
9196
|
+
let output = "";
|
|
9197
|
+
const collect = (chunk) => {
|
|
9198
|
+
const text = chunk.toString();
|
|
9199
|
+
opts.onOutput?.(text);
|
|
9200
|
+
output = (output + text).slice(-LOG_TAIL_CHARS);
|
|
9201
|
+
};
|
|
9202
|
+
let child;
|
|
9203
|
+
try {
|
|
9204
|
+
child = (0, import_node_child_process3.spawn)(pm, args, {
|
|
9205
|
+
// Electron's bundled Node has no shell PATH resolution for `npm.cmd` on Windows.
|
|
9206
|
+
shell: process.platform === "win32",
|
|
9207
|
+
env: { ...process.env, ELECTRON_RUN_AS_NODE: void 0 },
|
|
9208
|
+
windowsHide: true
|
|
9209
|
+
});
|
|
9210
|
+
} catch (err) {
|
|
9211
|
+
resolve({ ok: false, code: null, command, output: err.message });
|
|
9212
|
+
return;
|
|
9213
|
+
}
|
|
9214
|
+
const timer = setTimeout(() => child.kill(), INSTALL_TIMEOUT_MS);
|
|
9215
|
+
child.stdout?.on("data", collect);
|
|
9216
|
+
child.stderr?.on("data", collect);
|
|
9217
|
+
child.on("error", (err) => {
|
|
9218
|
+
clearTimeout(timer);
|
|
9219
|
+
resolve({ ok: false, code: null, command, output: output + err.message });
|
|
9220
|
+
});
|
|
9221
|
+
child.on("close", (code2) => {
|
|
9222
|
+
clearTimeout(timer);
|
|
9223
|
+
resolve({ ok: code2 === 0, code: code2, command, output });
|
|
9224
|
+
});
|
|
9225
|
+
});
|
|
9226
|
+
}
|
|
9227
|
+
|
|
8974
9228
|
// src/core/engine.ts
|
|
8975
9229
|
var SHALLOW_MS = 3e3;
|
|
8976
9230
|
var DEEP_MS = 6e4;
|
|
@@ -8978,6 +9232,7 @@ var TICK_MS = 2e3;
|
|
|
8978
9232
|
var REMOTE_MS = 6e4;
|
|
8979
9233
|
var LIVE_LIMITS_DEBOUNCE_MS = 1e4;
|
|
8980
9234
|
var LIVE_LIMITS_MIN_GAP_MS = 2e4;
|
|
9235
|
+
var UPDATE_CHECK_MS = 6 * 60 * 60 * 1e3;
|
|
8981
9236
|
var Engine = class extends import_node_events.EventEmitter {
|
|
8982
9237
|
constructor(opts) {
|
|
8983
9238
|
super();
|
|
@@ -9014,6 +9269,9 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
9014
9269
|
liveLimitsInFlight = false;
|
|
9015
9270
|
liveLimitsTimer = null;
|
|
9016
9271
|
lastLiveLimitsAttempt = 0;
|
|
9272
|
+
update = null;
|
|
9273
|
+
updateStatus = "idle";
|
|
9274
|
+
updateLog = null;
|
|
9017
9275
|
get heatmapWeeks() {
|
|
9018
9276
|
return this.opts.heatmapWeeks ?? 16;
|
|
9019
9277
|
}
|
|
@@ -9035,6 +9293,8 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
9035
9293
|
return;
|
|
9036
9294
|
}
|
|
9037
9295
|
void this.refreshLiveLimits(true);
|
|
9296
|
+
void this.checkUpdate(false);
|
|
9297
|
+
this.timers.push(setInterval(() => void this.checkUpdate(false), UPDATE_CHECK_MS));
|
|
9038
9298
|
this.store.startWatching(() => void this.refresh(false));
|
|
9039
9299
|
this.timers.push(setInterval(() => void this.refresh(false), SHALLOW_MS));
|
|
9040
9300
|
this.timers.push(setInterval(() => void this.refresh(true), DEEP_MS));
|
|
@@ -9141,7 +9401,7 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
9141
9401
|
if (!this.signedIn) return { buckets: 0, sessions: 0 };
|
|
9142
9402
|
if (!this.stats) this.recompute();
|
|
9143
9403
|
try {
|
|
9144
|
-
const r = await this.uploader.pushAll(this.stats.buckets, this.
|
|
9404
|
+
const r = await this.uploader.pushAll(this.stats.buckets, this.stats.sessions, this.stats.sessionCosts);
|
|
9145
9405
|
this.emitSnapshot();
|
|
9146
9406
|
return r;
|
|
9147
9407
|
} catch (err) {
|
|
@@ -9165,6 +9425,48 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
9165
9425
|
await this.uploader.fetchRemote(this.heatmapWeeks);
|
|
9166
9426
|
this.recompute();
|
|
9167
9427
|
}
|
|
9428
|
+
/**
|
|
9429
|
+
* Ask the npm registry for the newest published version (cached for 6 h unless `force`).
|
|
9430
|
+
* Best-effort: a failure is recorded on the snapshot, never thrown.
|
|
9431
|
+
*/
|
|
9432
|
+
async checkUpdate(force) {
|
|
9433
|
+
if (!this.config.checkUpdates) {
|
|
9434
|
+
this.update = null;
|
|
9435
|
+
return null;
|
|
9436
|
+
}
|
|
9437
|
+
if (this.updateStatus === "checking" || this.updateStatus === "installing") return this.update;
|
|
9438
|
+
this.updateStatus = "checking";
|
|
9439
|
+
this.emitSnapshot();
|
|
9440
|
+
this.update = await checkForUpdate({ force });
|
|
9441
|
+
this.updateStatus = "idle";
|
|
9442
|
+
if (this.update.error) this.opts.log?.(`update check failed: ${this.update.error}`);
|
|
9443
|
+
this.emitSnapshot();
|
|
9444
|
+
return this.update;
|
|
9445
|
+
}
|
|
9446
|
+
/**
|
|
9447
|
+
* Install the newest version globally with the package manager this copy came from.
|
|
9448
|
+
* The new code only takes effect once the app is restarted, so the UI says so rather than
|
|
9449
|
+
* pretending to hot-swap itself.
|
|
9450
|
+
*/
|
|
9451
|
+
async installUpdate() {
|
|
9452
|
+
if (this.updateStatus === "installing") return false;
|
|
9453
|
+
if (!this.update?.available) await this.checkUpdate(true);
|
|
9454
|
+
if (!this.update?.available) return false;
|
|
9455
|
+
this.updateStatus = "installing";
|
|
9456
|
+
this.updateLog = null;
|
|
9457
|
+
this.emitSnapshot();
|
|
9458
|
+
const r = await runUpdate({ version: this.update.latest ?? void 0 });
|
|
9459
|
+
this.updateStatus = r.ok ? "installed" : "failed";
|
|
9460
|
+
this.updateLog = r.ok ? null : `${r.command}
|
|
9461
|
+
${r.output}`.trim();
|
|
9462
|
+
if (!r.ok) this.opts.log?.(`update install failed (${r.code}): ${r.output}`);
|
|
9463
|
+
this.emitSnapshot();
|
|
9464
|
+
return r.ok;
|
|
9465
|
+
}
|
|
9466
|
+
updateState() {
|
|
9467
|
+
if (!this.config.checkUpdates || !this.update) return null;
|
|
9468
|
+
return { ...this.update, status: this.updateStatus, log: this.updateLog };
|
|
9469
|
+
}
|
|
9168
9470
|
/** Start the device-code login flow (resolves when approved / denied / expired / cancelled). */
|
|
9169
9471
|
async login(openBrowser, onCode) {
|
|
9170
9472
|
if (this.pending) return { status: "cancelled" };
|
|
@@ -9257,6 +9559,7 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
9257
9559
|
rateLimits: this.liveLimits ?? fromLogRateLimits(s.logRateLimits),
|
|
9258
9560
|
rateLimitsError: this.config.liveRateLimits ? this.liveLimitsError : null,
|
|
9259
9561
|
rateLimitsUpdatedAt: this.liveLimits ? this.liveLimitsAt : s.logRateLimits?.observedAt ?? null,
|
|
9562
|
+
update: this.updateState(),
|
|
9260
9563
|
modelsToday: s.modelsToday,
|
|
9261
9564
|
modelsMonth: s.modelsMonth,
|
|
9262
9565
|
byAgentToday: s.byAgentToday,
|
|
@@ -9281,9 +9584,9 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
9281
9584
|
|
|
9282
9585
|
// src/main.ts
|
|
9283
9586
|
var DEBUG = Boolean(process.env.CODEX_TRACKER_DEBUG);
|
|
9284
|
-
var assetsDir =
|
|
9285
|
-
var rendererIndex =
|
|
9286
|
-
var preloadPath =
|
|
9587
|
+
var assetsDir = import_node_path11.default.join(__dirname, "..", "assets");
|
|
9588
|
+
var rendererIndex = import_node_path11.default.join(__dirname, "renderer", "index.html");
|
|
9589
|
+
var preloadPath = import_node_path11.default.join(__dirname, "preload.js");
|
|
9287
9590
|
var isMac = process.platform === "darwin";
|
|
9288
9591
|
var isWin = process.platform === "win32";
|
|
9289
9592
|
var mb = null;
|
|
@@ -9294,12 +9597,12 @@ function log2(...args) {
|
|
|
9294
9597
|
}
|
|
9295
9598
|
function trayIcon() {
|
|
9296
9599
|
if (isMac) {
|
|
9297
|
-
const img2 = import_electron.nativeImage.createFromPath(
|
|
9600
|
+
const img2 = import_electron.nativeImage.createFromPath(import_node_path11.default.join(assetsDir, "trayTemplate.png"));
|
|
9298
9601
|
img2.setTemplateImage(true);
|
|
9299
9602
|
return img2;
|
|
9300
9603
|
}
|
|
9301
9604
|
const file = import_electron.nativeTheme.shouldUseDarkColors || !isWin ? "tray-win.png" : "tray-win-light.png";
|
|
9302
|
-
const img = import_electron.nativeImage.createFromPath(
|
|
9605
|
+
const img = import_electron.nativeImage.createFromPath(import_node_path11.default.join(assetsDir, file));
|
|
9303
9606
|
return isWin ? img.resize({ width: 16, height: 16 }) : img;
|
|
9304
9607
|
}
|
|
9305
9608
|
function trayText(s) {
|
|
@@ -9319,7 +9622,7 @@ function updateTray(s) {
|
|
|
9319
9622
|
mb.tray.setToolTip(`${APP_NAME} \u2014 ${t(L, "today")}: ${formatTokens(s.today.usage.total)} \xB7 ${formatUSD(s.today.cost)}${live}`);
|
|
9320
9623
|
if (isMac) mb.tray.setTitle(trayText(s), { fontType: "monospacedDigit" });
|
|
9321
9624
|
}
|
|
9322
|
-
var launchAgentPath =
|
|
9625
|
+
var launchAgentPath = import_node_path11.default.join(import_node_os7.default.homedir(), "Library", "LaunchAgents", "dev.codex-tracker.menubar.plist");
|
|
9323
9626
|
function setLaunchAtLogin(enabled) {
|
|
9324
9627
|
if (isMac) {
|
|
9325
9628
|
if (enabled) {
|
|
@@ -9327,20 +9630,20 @@ function setLaunchAtLogin(enabled) {
|
|
|
9327
9630
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
9328
9631
|
<plist version="1.0"><dict>
|
|
9329
9632
|
<key>Label</key><string>dev.codex-tracker.menubar</string>
|
|
9330
|
-
<key>ProgramArguments</key><array><string>${process.execPath}</string><string>${
|
|
9633
|
+
<key>ProgramArguments</key><array><string>${process.execPath}</string><string>${import_node_path11.default.join(__dirname, "main.js")}</string></array>
|
|
9331
9634
|
<key>RunAtLoad</key><true/>
|
|
9332
9635
|
<key>KeepAlive</key><false/>
|
|
9333
9636
|
<key>ProcessType</key><string>Interactive</string>
|
|
9334
9637
|
</dict></plist>
|
|
9335
9638
|
`;
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
(0,
|
|
9639
|
+
import_node_fs9.default.mkdirSync(import_node_path11.default.dirname(launchAgentPath), { recursive: true });
|
|
9640
|
+
import_node_fs9.default.writeFileSync(launchAgentPath, plist);
|
|
9641
|
+
(0, import_node_child_process4.execFile)("launchctl", ["load", "-w", launchAgentPath], () => {
|
|
9339
9642
|
});
|
|
9340
|
-
} else if (
|
|
9341
|
-
(0,
|
|
9643
|
+
} else if (import_node_fs9.default.existsSync(launchAgentPath)) {
|
|
9644
|
+
(0, import_node_child_process4.execFile)("launchctl", ["unload", "-w", launchAgentPath], () => {
|
|
9342
9645
|
try {
|
|
9343
|
-
|
|
9646
|
+
import_node_fs9.default.unlinkSync(launchAgentPath);
|
|
9344
9647
|
} catch {
|
|
9345
9648
|
}
|
|
9346
9649
|
});
|
|
@@ -9348,7 +9651,7 @@ function setLaunchAtLogin(enabled) {
|
|
|
9348
9651
|
return;
|
|
9349
9652
|
}
|
|
9350
9653
|
if (isWin) {
|
|
9351
|
-
import_electron.app.setLoginItemSettings({ openAtLogin: enabled, path: process.execPath, args: [
|
|
9654
|
+
import_electron.app.setLoginItemSettings({ openAtLogin: enabled, path: process.execPath, args: [import_node_path11.default.join(__dirname, "main.js")] });
|
|
9352
9655
|
}
|
|
9353
9656
|
}
|
|
9354
9657
|
function buildMenu(s) {
|
|
@@ -9385,6 +9688,9 @@ function buildMenu(s) {
|
|
|
9385
9688
|
}
|
|
9386
9689
|
] : [],
|
|
9387
9690
|
{ label: t(L, "refresh"), click: () => void engine?.refresh(true) },
|
|
9691
|
+
...s.update ? [
|
|
9692
|
+
s.update.available ? { label: t(L, "updateAvailable", { version: s.update.latest ?? "?" }), click: () => void engine?.installUpdate() } : { label: t(L, "checkForUpdates"), click: () => void engine?.checkUpdate(true) }
|
|
9693
|
+
] : [],
|
|
9388
9694
|
{ type: "separator" },
|
|
9389
9695
|
{ label: t(L, "quit"), click: () => import_electron.app.quit() }
|
|
9390
9696
|
];
|
|
@@ -9441,6 +9747,8 @@ if (!import_electron.app.requestSingleInstanceLock()) {
|
|
|
9441
9747
|
import_electron.ipcMain.handle("auth:cancel", () => engine?.cancelLogin());
|
|
9442
9748
|
import_electron.ipcMain.handle("auth:logout", () => engine?.logout());
|
|
9443
9749
|
import_electron.ipcMain.handle("refresh", () => engine?.refresh(true).then(() => void 0));
|
|
9750
|
+
import_electron.ipcMain.handle("update:check", () => engine?.checkUpdate(true).then(() => void 0));
|
|
9751
|
+
import_electron.ipcMain.handle("update:install", () => engine?.installUpdate().then(() => void 0));
|
|
9444
9752
|
import_electron.ipcMain.handle("quit", () => import_electron.app.quit());
|
|
9445
9753
|
mb.on("ready", async () => {
|
|
9446
9754
|
if (isMac) import_electron.app.dock?.hide();
|
|
@@ -9466,7 +9774,7 @@ if (!import_electron.app.requestSingleInstanceLock()) {
|
|
|
9466
9774
|
setTimeout(async () => {
|
|
9467
9775
|
try {
|
|
9468
9776
|
const img = await mb.window.webContents.capturePage();
|
|
9469
|
-
|
|
9777
|
+
import_node_fs9.default.writeFileSync(shot, img.toPNG());
|
|
9470
9778
|
log2("screenshot written", shot);
|
|
9471
9779
|
} catch (err) {
|
|
9472
9780
|
log2("screenshot failed", errorMessage(err));
|
|
@@ -9482,7 +9790,7 @@ if (!import_electron.app.requestSingleInstanceLock()) {
|
|
|
9482
9790
|
return { action: "deny" };
|
|
9483
9791
|
});
|
|
9484
9792
|
win.webContents.on("console-message", (_e, level, message, line, sourceId) => {
|
|
9485
|
-
if (DEBUG || level >= 2) console.error(`[renderer:${level}] ${message} (${
|
|
9793
|
+
if (DEBUG || level >= 2) console.error(`[renderer:${level}] ${message} (${import_node_path11.default.basename(sourceId)}:${line})`);
|
|
9486
9794
|
});
|
|
9487
9795
|
win.webContents.on("render-process-gone", (_e, details) => log2("renderer gone", details));
|
|
9488
9796
|
if (process.env.CODEX_TRACKER_DEVTOOLS) win.webContents.openDevTools({ mode: "detach" });
|