codex-token-tracker 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/README.md +100 -8
- package/dist/cli.js +664 -80
- package/dist/main.js +604 -89
- package/dist/preload.js +3 -0
- package/dist/renderer/renderer.js +235 -5
- package/dist/renderer/styles.css +129 -0
- package/package.json +5 -2
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;
|
|
@@ -1099,9 +1138,20 @@ var init_src = __esm({
|
|
|
1099
1138
|
}
|
|
1100
1139
|
});
|
|
1101
1140
|
|
|
1141
|
+
// src/version.ts
|
|
1142
|
+
var APP_VERSION, APP_CHANNEL, IS_DEV_BUILD;
|
|
1143
|
+
var init_version = __esm({
|
|
1144
|
+
"src/version.ts"() {
|
|
1145
|
+
"use strict";
|
|
1146
|
+
APP_VERSION = true ? "0.2.1" : "0.0.0-dev";
|
|
1147
|
+
APP_CHANNEL = true ? "prod" : "dev";
|
|
1148
|
+
IS_DEV_BUILD = APP_CHANNEL === "dev";
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1102
1152
|
// src/core/config.ts
|
|
1103
1153
|
function configDir() {
|
|
1104
|
-
return process.env.CODEX_TRACKER_HOME || import_node_path2.default.join(import_node_os2.default.homedir(), ".codex-tracker");
|
|
1154
|
+
return process.env.CODEX_TRACKER_HOME || import_node_path2.default.join(import_node_os2.default.homedir(), IS_DEV_BUILD ? ".codex-tracker-dev" : ".codex-tracker");
|
|
1105
1155
|
}
|
|
1106
1156
|
function readJson(file, fallback) {
|
|
1107
1157
|
try {
|
|
@@ -1154,6 +1204,7 @@ function loadConfig() {
|
|
|
1154
1204
|
cfg.sources = normalizeSources(stored.sources);
|
|
1155
1205
|
cfg.trackAllProviders = stored.trackAllProviders === true;
|
|
1156
1206
|
cfg.liveRateLimits = stored.liveRateLimits !== false;
|
|
1207
|
+
cfg.checkUpdates = typeof stored.checkUpdates === "boolean" ? stored.checkUpdates : DEFAULT_CONFIG.checkUpdates;
|
|
1157
1208
|
if (!(cfg.usageRefreshSec >= 15)) cfg.usageRefreshSec = DEFAULT_CONFIG.usageRefreshSec;
|
|
1158
1209
|
if (!["auto", "en", "zh"].includes(cfg.language)) cfg.language = "auto";
|
|
1159
1210
|
if (!(cfg.uploadIntervalSec >= 10)) cfg.uploadIntervalSec = DEFAULT_CONFIG.uploadIntervalSec;
|
|
@@ -1183,6 +1234,7 @@ function coerceConfigValue(key, raw) {
|
|
|
1183
1234
|
case "launchAtLogin":
|
|
1184
1235
|
case "trackAllProviders":
|
|
1185
1236
|
case "liveRateLimits":
|
|
1237
|
+
case "checkUpdates":
|
|
1186
1238
|
return parseBool(raw);
|
|
1187
1239
|
case "extraSessionDirs": {
|
|
1188
1240
|
const trimmed = raw.trim();
|
|
@@ -1259,14 +1311,17 @@ function loadPricingOverrides() {
|
|
|
1259
1311
|
}
|
|
1260
1312
|
return Object.keys(out).length ? out : void 0;
|
|
1261
1313
|
}
|
|
1262
|
-
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;
|
|
1314
|
+
var import_node_fs2, import_node_os2, import_node_path2, PROD_DASHBOARD_URL, DEV_DASHBOARD_URL, DEFAULT_DASHBOARD_URL, SOURCE_FORMATS, DEFAULT_SOURCES, SOURCE_IDS, DEFAULT_CONFIG, EDITABLE_KEYS, AGENT_NAME_RE;
|
|
1263
1315
|
var init_config = __esm({
|
|
1264
1316
|
"src/core/config.ts"() {
|
|
1265
1317
|
"use strict";
|
|
1266
1318
|
import_node_fs2 = __toESM(require("node:fs"));
|
|
1267
1319
|
import_node_os2 = __toESM(require("node:os"));
|
|
1268
1320
|
import_node_path2 = __toESM(require("node:path"));
|
|
1269
|
-
|
|
1321
|
+
init_version();
|
|
1322
|
+
PROD_DASHBOARD_URL = "https://codex.chenli.dev";
|
|
1323
|
+
DEV_DASHBOARD_URL = "http://localhost:3000";
|
|
1324
|
+
DEFAULT_DASHBOARD_URL = IS_DEV_BUILD ? DEV_DASHBOARD_URL : PROD_DASHBOARD_URL;
|
|
1270
1325
|
SOURCE_FORMATS = ["codex", "pi", "generic", "opencode", "cline"];
|
|
1271
1326
|
DEFAULT_SOURCES = { codex: true, pi: true, hermes: true, opencode: true, cline: true, roo: true, kilo: true };
|
|
1272
1327
|
SOURCE_IDS = Object.keys(DEFAULT_SOURCES);
|
|
@@ -1285,7 +1340,9 @@ var init_config = __esm({
|
|
|
1285
1340
|
sources: { ...DEFAULT_SOURCES },
|
|
1286
1341
|
trackAllProviders: false,
|
|
1287
1342
|
liveRateLimits: true,
|
|
1288
|
-
usageRefreshSec: 60
|
|
1343
|
+
usageRefreshSec: 60,
|
|
1344
|
+
// a dev build must never offer to replace itself with the published package
|
|
1345
|
+
checkUpdates: !IS_DEV_BUILD
|
|
1289
1346
|
};
|
|
1290
1347
|
EDITABLE_KEYS = [
|
|
1291
1348
|
"dashboardUrl",
|
|
@@ -1298,7 +1355,8 @@ var init_config = __esm({
|
|
|
1298
1355
|
"sources",
|
|
1299
1356
|
"trackAllProviders",
|
|
1300
1357
|
"liveRateLimits",
|
|
1301
|
-
"usageRefreshSec"
|
|
1358
|
+
"usageRefreshSec",
|
|
1359
|
+
"checkUpdates"
|
|
1302
1360
|
];
|
|
1303
1361
|
AGENT_NAME_RE = /^[a-z0-9][a-z0-9_.-]{0,31}$/;
|
|
1304
1362
|
}
|
|
@@ -1354,6 +1412,7 @@ var init_en = __esm({
|
|
|
1354
1412
|
estimatedPricingHint: "Pricing estimated: model not in the price table",
|
|
1355
1413
|
other: "Other",
|
|
1356
1414
|
lastUpload: "Last upload {time}",
|
|
1415
|
+
lastSync: "Last full sync {time}",
|
|
1357
1416
|
neverUploaded: "Not uploaded yet",
|
|
1358
1417
|
uploadError: "Upload error: {message}",
|
|
1359
1418
|
allDevicesToday: "All devices today",
|
|
@@ -1372,16 +1431,44 @@ var init_en = __esm({
|
|
|
1372
1431
|
launchAtLogin: "Launch at login",
|
|
1373
1432
|
refresh: "Refresh",
|
|
1374
1433
|
quit: "Quit",
|
|
1434
|
+
// full sync
|
|
1435
|
+
syncNow: "Sync now",
|
|
1436
|
+
syncing: "Syncing\u2026",
|
|
1437
|
+
syncTitle: "Rescan every agent on this device and re-upload its full usage history",
|
|
1438
|
+
syncScanning: "Rescanning every agent's transcripts\u2026",
|
|
1439
|
+
syncComputing: "Recomputing token usage\u2026",
|
|
1440
|
+
syncUploading: "Re-uploading this device's history\u2026",
|
|
1441
|
+
syncDownloading: "Fetching other devices\u2026",
|
|
1442
|
+
syncLimits: "Refreshing rate limits\u2026",
|
|
1443
|
+
syncDone: "Synced {agents} \xB7 {files} files \xB7 {sessions} sessions \xB7 {buckets} hours re-uploaded",
|
|
1444
|
+
syncDoneLocal: "Rescanned {agents} \xB7 {files} files \xB7 {sessions} sessions \u2014 sign in to upload",
|
|
1445
|
+
syncFailed: "Sync failed: {message}",
|
|
1375
1446
|
sessions: "{n} sessions",
|
|
1376
1447
|
files: "{n} files",
|
|
1377
1448
|
noData: "No usage found yet. Run Codex and this will fill in.",
|
|
1378
1449
|
version: "v{version}",
|
|
1450
|
+
// dev builds
|
|
1451
|
+
devBuild: "DEV",
|
|
1452
|
+
devBuildTitle: "Local build \u2014 uploading to {url} (dev environment). Config: {dir}",
|
|
1453
|
+
devBuildMenu: "Dev build \u2192 {url}",
|
|
1379
1454
|
justNow: "just now",
|
|
1380
1455
|
never: "never",
|
|
1381
1456
|
local: "Local",
|
|
1382
1457
|
remote: "Other devices",
|
|
1458
|
+
// updates
|
|
1459
|
+
update: "Update",
|
|
1460
|
+
updateAvailable: "Update to v{version}",
|
|
1461
|
+
updateNewVersion: "Version {version} is available (you have v{current})",
|
|
1462
|
+
updateUpToDate: "Up to date",
|
|
1463
|
+
updateChecking: "Checking\u2026",
|
|
1464
|
+
updateInstalling: "Installing\u2026",
|
|
1465
|
+
updateInstalled: "Updated to v{version} \u2014 restart to apply",
|
|
1466
|
+
updateFailed: "Update failed \u2014 run this yourself:",
|
|
1467
|
+
updateCheckFailed: "Update check failed: {message}",
|
|
1468
|
+
checkForUpdates: "Check for updates",
|
|
1469
|
+
releaseNotes: "Release notes",
|
|
1383
1470
|
// CLI
|
|
1384
|
-
cliUsage: `Usage: codex-tracker [command] [options]
|
|
1471
|
+
cliUsage: `Usage: codex-token-tracker [command] [options] (alias: codex-tracker)
|
|
1385
1472
|
|
|
1386
1473
|
Commands:
|
|
1387
1474
|
(none) Start the menu bar app (falls back to agent mode without a display)
|
|
@@ -1391,11 +1478,14 @@ Commands:
|
|
|
1391
1478
|
login Connect this device to your dashboard (--dashboard <url>)
|
|
1392
1479
|
logout Disconnect this device
|
|
1393
1480
|
status Print today's usage, live session and rate limits
|
|
1481
|
+
sync Rescan every agent and re-upload this device's full history (calibrate)
|
|
1394
1482
|
paths Show detected session directories (Codex, pi, OpenCode, Cline/Roo/Kilo, Hermes, custom)
|
|
1483
|
+
update Install the newest published version (--check only reports)
|
|
1395
1484
|
config get|set Read or change settings (config set uploadIntervalSec 30, config set sources.pi false)
|
|
1396
1485
|
lang <en|zh|auto> Set the display language
|
|
1397
1486
|
|
|
1398
1487
|
Options:
|
|
1488
|
+
--check update: report the newest version without installing it
|
|
1399
1489
|
--dashboard <url> Dashboard URL (self-hosted)
|
|
1400
1490
|
--background Start the menu bar app detached and return
|
|
1401
1491
|
--version, -v Print version
|
|
@@ -1437,7 +1527,21 @@ Options:
|
|
|
1437
1527
|
cliConfigSet: "Set {key} = {value}",
|
|
1438
1528
|
cliConfigUnknownKey: "Unknown config key: {key}. Keys: {keys}",
|
|
1439
1529
|
cliLangSet: "Language set to {lang}",
|
|
1440
|
-
cliConfigDir: "Config: {dir}"
|
|
1530
|
+
cliConfigDir: "Config: {dir}",
|
|
1531
|
+
cliUpdateLatest: "codex-token-tracker {version} is the latest version.",
|
|
1532
|
+
cliUpdateAvailable: "Update available: {current} \u2192 {latest}",
|
|
1533
|
+
cliUpdateRunning: "Running: {command}",
|
|
1534
|
+
cliUpdateDone: "Updated to {version}. Restart the menu bar app (or rerun the CLI) to use it.",
|
|
1535
|
+
cliUpdateFailed: "Update failed (exit {code}). Run it yourself: {command}",
|
|
1536
|
+
cliUpdateCheckFailed: "Could not reach the npm registry: {message}",
|
|
1537
|
+
cliSyncStart: "Full sync: rescanning every agent on this device\u2026",
|
|
1538
|
+
cliSyncScanned: " scanned {files} files in {roots} directories ({agents})",
|
|
1539
|
+
cliSyncUploaded: " re-uploaded {buckets} hour buckets and {sessions} sessions",
|
|
1540
|
+
cliSyncLocal: " not signed in \u2014 nothing uploaded (run `codex-tracker login` first)",
|
|
1541
|
+
cliSyncDone: "Sync complete in {seconds}s \u2014 {sessions} sessions, today {tokens} tok \xB7 {cost}",
|
|
1542
|
+
cliSyncFailed: "Sync failed: {message}",
|
|
1543
|
+
cliChannelDev: "Dev build \u2014 dashboard {url} \xB7 config {dir}",
|
|
1544
|
+
cliUpdateDevBuild: "This is a local dev build; `update` would install the published package over it. Run `pnpm build` in the repo instead."
|
|
1441
1545
|
};
|
|
1442
1546
|
}
|
|
1443
1547
|
});
|
|
@@ -1491,6 +1595,7 @@ var init_zh = __esm({
|
|
|
1491
1595
|
estimatedPricingHint: "\u4EF7\u683C\u4E3A\u4F30\u7B97\u503C\uFF1A\u8BE5\u6A21\u578B\u4E0D\u5728\u4EF7\u76EE\u8868\u4E2D",
|
|
1492
1596
|
other: "\u5176\u4ED6",
|
|
1493
1597
|
lastUpload: "\u4E0A\u6B21\u4E0A\u4F20 {time}",
|
|
1598
|
+
lastSync: "\u4E0A\u6B21\u5B8C\u6574\u540C\u6B65 {time}",
|
|
1494
1599
|
neverUploaded: "\u5C1A\u672A\u4E0A\u4F20",
|
|
1495
1600
|
uploadError: "\u4E0A\u4F20\u5931\u8D25\uFF1A{message}",
|
|
1496
1601
|
allDevicesToday: "\u6240\u6709\u8BBE\u5907\u4ECA\u65E5",
|
|
@@ -1509,15 +1614,42 @@ var init_zh = __esm({
|
|
|
1509
1614
|
launchAtLogin: "\u5F00\u673A\u81EA\u542F\u52A8",
|
|
1510
1615
|
refresh: "\u5237\u65B0",
|
|
1511
1616
|
quit: "\u9000\u51FA",
|
|
1617
|
+
// 完整同步
|
|
1618
|
+
syncNow: "\u7ACB\u5373\u540C\u6B65",
|
|
1619
|
+
syncing: "\u540C\u6B65\u4E2D\u2026",
|
|
1620
|
+
syncTitle: "\u91CD\u65B0\u626B\u63CF\u672C\u8BBE\u5907\u4E0A\u7684\u6240\u6709\u667A\u80FD\u4F53\uFF0C\u5E76\u91CD\u65B0\u4E0A\u4F20\u5B8C\u6574\u7528\u91CF\u5386\u53F2",
|
|
1621
|
+
syncScanning: "\u6B63\u5728\u91CD\u65B0\u626B\u63CF\u6240\u6709\u667A\u80FD\u4F53\u7684\u4F1A\u8BDD\u8BB0\u5F55\u2026",
|
|
1622
|
+
syncComputing: "\u6B63\u5728\u91CD\u65B0\u8BA1\u7B97 token \u7528\u91CF\u2026",
|
|
1623
|
+
syncUploading: "\u6B63\u5728\u91CD\u65B0\u4E0A\u4F20\u672C\u8BBE\u5907\u7684\u5386\u53F2\u6570\u636E\u2026",
|
|
1624
|
+
syncDownloading: "\u6B63\u5728\u62C9\u53D6\u5176\u4ED6\u8BBE\u5907\u7684\u6570\u636E\u2026",
|
|
1625
|
+
syncLimits: "\u6B63\u5728\u5237\u65B0\u9650\u989D\u2026",
|
|
1626
|
+
syncDone: "\u5DF2\u540C\u6B65 {agents} \xB7 {files} \u4E2A\u6587\u4EF6 \xB7 {sessions} \u4E2A\u4F1A\u8BDD \xB7 \u91CD\u65B0\u4E0A\u4F20 {buckets} \u4E2A\u5C0F\u65F6\u6BB5",
|
|
1627
|
+
syncDoneLocal: "\u5DF2\u91CD\u65B0\u626B\u63CF {agents} \xB7 {files} \u4E2A\u6587\u4EF6 \xB7 {sessions} \u4E2A\u4F1A\u8BDD \u2014 \u767B\u5F55\u540E\u53EF\u4E0A\u4F20",
|
|
1628
|
+
syncFailed: "\u540C\u6B65\u5931\u8D25\uFF1A{message}",
|
|
1512
1629
|
sessions: "{n} \u4E2A\u4F1A\u8BDD",
|
|
1513
1630
|
files: "{n} \u4E2A\u6587\u4EF6",
|
|
1514
1631
|
noData: "\u8FD8\u6CA1\u6709\u7528\u91CF\u6570\u636E\u3002\u8FD0\u884C Codex \u540E\u8FD9\u91CC\u4F1A\u81EA\u52A8\u66F4\u65B0\u3002",
|
|
1515
1632
|
version: "v{version}",
|
|
1633
|
+
// 开发版
|
|
1634
|
+
devBuild: "\u5F00\u53D1\u7248",
|
|
1635
|
+
devBuildTitle: "\u672C\u5730\u6784\u5EFA \u2014\u2014 \u6570\u636E\u4E0A\u4F20\u81F3 {url}\uFF08\u5F00\u53D1\u73AF\u5883\uFF09\u3002\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}",
|
|
1636
|
+
devBuildMenu: "\u5F00\u53D1\u7248 \u2192 {url}",
|
|
1516
1637
|
justNow: "\u521A\u521A",
|
|
1517
1638
|
never: "\u4ECE\u672A",
|
|
1518
1639
|
local: "\u672C\u673A",
|
|
1519
1640
|
remote: "\u5176\u4ED6\u8BBE\u5907",
|
|
1520
|
-
|
|
1641
|
+
update: "\u66F4\u65B0",
|
|
1642
|
+
updateAvailable: "\u66F4\u65B0\u5230 v{version}",
|
|
1643
|
+
updateNewVersion: "\u6709\u65B0\u7248\u672C {version}\uFF08\u5F53\u524D v{current}\uFF09",
|
|
1644
|
+
updateUpToDate: "\u5DF2\u662F\u6700\u65B0\u7248\u672C",
|
|
1645
|
+
updateChecking: "\u68C0\u67E5\u4E2D\u2026",
|
|
1646
|
+
updateInstalling: "\u5B89\u88C5\u4E2D\u2026",
|
|
1647
|
+
updateInstalled: "\u5DF2\u66F4\u65B0\u5230 v{version} \u2014 \u91CD\u542F\u540E\u751F\u6548",
|
|
1648
|
+
updateFailed: "\u66F4\u65B0\u5931\u8D25 \u2014 \u8BF7\u624B\u52A8\u6267\u884C\uFF1A",
|
|
1649
|
+
updateCheckFailed: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF1A{message}",
|
|
1650
|
+
checkForUpdates: "\u68C0\u67E5\u66F4\u65B0",
|
|
1651
|
+
releaseNotes: "\u66F4\u65B0\u65E5\u5FD7",
|
|
1652
|
+
cliUsage: `\u7528\u6CD5\uFF1Acodex-token-tracker [\u547D\u4EE4] [\u9009\u9879] \uFF08\u522B\u540D\uFF1Acodex-tracker\uFF09
|
|
1521
1653
|
|
|
1522
1654
|
\u547D\u4EE4\uFF1A
|
|
1523
1655
|
(\u65E0) \u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\uFF08\u65E0\u663E\u793A\u73AF\u5883\u65F6\u81EA\u52A8\u5207\u6362\u4E3A agent \u6A21\u5F0F\uFF09
|
|
@@ -1527,11 +1659,14 @@ var init_zh = __esm({
|
|
|
1527
1659
|
login \u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230\u4EEA\u8868\u76D8\uFF08--dashboard <url>\uFF09
|
|
1528
1660
|
logout \u65AD\u5F00\u672C\u8BBE\u5907
|
|
1529
1661
|
status \u6253\u5370\u4ECA\u65E5\u7528\u91CF\u3001\u5B9E\u65F6\u4F1A\u8BDD\u4E0E\u9650\u989D
|
|
1662
|
+
sync \u91CD\u65B0\u626B\u63CF\u6240\u6709\u667A\u80FD\u4F53\u5E76\u91CD\u65B0\u4E0A\u4F20\u672C\u8BBE\u5907\u5B8C\u6574\u5386\u53F2\uFF08\u6821\u51C6\uFF09
|
|
1530
1663
|
paths \u663E\u793A\u68C0\u6D4B\u5230\u7684\u4F1A\u8BDD\u76EE\u5F55\uFF08Codex\u3001pi\u3001OpenCode\u3001Cline/Roo/Kilo\u3001Hermes\u3001\u81EA\u5B9A\u4E49\uFF09
|
|
1664
|
+
update \u5B89\u88C5\u6700\u65B0\u53D1\u5E03\u7248\u672C\uFF08--check \u53EA\u68C0\u67E5\u4E0D\u5B89\u88C5\uFF09
|
|
1531
1665
|
config get|set \u8BFB\u53D6\u6216\u4FEE\u6539\u8BBE\u7F6E\uFF08config set uploadIntervalSec 30\uFF0Cconfig set sources.pi false\uFF09
|
|
1532
1666
|
lang <en|zh|auto> \u8BBE\u7F6E\u663E\u793A\u8BED\u8A00
|
|
1533
1667
|
|
|
1534
1668
|
\u9009\u9879\uFF1A
|
|
1669
|
+
--check update\uFF1A\u53EA\u62A5\u544A\u6700\u65B0\u7248\u672C\uFF0C\u4E0D\u6267\u884C\u5B89\u88C5
|
|
1535
1670
|
--dashboard <url> \u4EEA\u8868\u76D8\u5730\u5740\uFF08\u81EA\u6258\u7BA1\uFF09
|
|
1536
1671
|
--background \u4EE5\u540E\u53F0\u65B9\u5F0F\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u5E76\u7ACB\u5373\u8FD4\u56DE
|
|
1537
1672
|
--version, -v \u663E\u793A\u7248\u672C
|
|
@@ -1573,7 +1708,21 @@ var init_zh = __esm({
|
|
|
1573
1708
|
cliConfigSet: "\u5DF2\u8BBE\u7F6E {key} = {value}",
|
|
1574
1709
|
cliConfigUnknownKey: "\u672A\u77E5\u914D\u7F6E\u9879\uFF1A{key}\u3002\u53EF\u7528\uFF1A{keys}",
|
|
1575
1710
|
cliLangSet: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A {lang}",
|
|
1576
|
-
cliConfigDir: "\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}"
|
|
1711
|
+
cliConfigDir: "\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}",
|
|
1712
|
+
cliUpdateLatest: "codex-token-tracker {version} \u5DF2\u662F\u6700\u65B0\u7248\u672C\u3002",
|
|
1713
|
+
cliUpdateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C\uFF1A{current} \u2192 {latest}",
|
|
1714
|
+
cliUpdateRunning: "\u6B63\u5728\u6267\u884C\uFF1A{command}",
|
|
1715
|
+
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",
|
|
1716
|
+
cliUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF1A{command}",
|
|
1717
|
+
cliUpdateCheckFailed: "\u65E0\u6CD5\u8BBF\u95EE npm registry\uFF1A{message}",
|
|
1718
|
+
cliSyncStart: "\u5B8C\u6574\u540C\u6B65\uFF1A\u6B63\u5728\u91CD\u65B0\u626B\u63CF\u672C\u8BBE\u5907\u4E0A\u7684\u6240\u6709\u667A\u80FD\u4F53\u2026",
|
|
1719
|
+
cliSyncScanned: " \u5DF2\u626B\u63CF {roots} \u4E2A\u76EE\u5F55\u4E2D\u7684 {files} \u4E2A\u6587\u4EF6\uFF08{agents}\uFF09",
|
|
1720
|
+
cliSyncUploaded: " \u5DF2\u91CD\u65B0\u4E0A\u4F20 {buckets} \u4E2A\u5C0F\u65F6\u6BB5\u4E0E {sessions} \u4E2A\u4F1A\u8BDD",
|
|
1721
|
+
cliSyncLocal: " \u672A\u767B\u5F55 \u2014 \u6CA1\u6709\u4E0A\u4F20\u4EFB\u4F55\u6570\u636E\uFF08\u8BF7\u5148\u8FD0\u884C `codex-tracker login`\uFF09",
|
|
1722
|
+
cliSyncDone: "\u540C\u6B65\u5B8C\u6210\uFF0C\u7528\u65F6 {seconds} \u79D2 \u2014 {sessions} \u4E2A\u4F1A\u8BDD\uFF0C\u4ECA\u65E5 {tokens} tok \xB7 {cost}",
|
|
1723
|
+
cliSyncFailed: "\u540C\u6B65\u5931\u8D25\uFF1A{message}",
|
|
1724
|
+
cliChannelDev: "\u5F00\u53D1\u7248 \u2014\u2014 \u4EEA\u8868\u76D8 {url} \xB7 \u914D\u7F6E\u76EE\u5F55 {dir}",
|
|
1725
|
+
cliUpdateDevBuild: "\u8FD9\u662F\u672C\u5730\u5F00\u53D1\u7248\uFF1B`update` \u4F1A\u7528\u5DF2\u53D1\u5E03\u7684\u5305\u8986\u76D6\u5B83\u3002\u8BF7\u6539\u4E3A\u5728\u4ED3\u5E93\u4E2D\u8FD0\u884C `pnpm build`\u3002"
|
|
1577
1726
|
};
|
|
1578
1727
|
}
|
|
1579
1728
|
});
|
|
@@ -3194,12 +3343,12 @@ function createApi(pathParts = []) {
|
|
|
3194
3343
|
`API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
|
|
3195
3344
|
);
|
|
3196
3345
|
}
|
|
3197
|
-
const
|
|
3346
|
+
const path13 = pathParts.slice(0, -1).join("/");
|
|
3198
3347
|
const exportName = pathParts[pathParts.length - 1];
|
|
3199
3348
|
if (exportName === "default") {
|
|
3200
|
-
return
|
|
3349
|
+
return path13;
|
|
3201
3350
|
} else {
|
|
3202
|
-
return
|
|
3351
|
+
return path13 + ":" + exportName;
|
|
3203
3352
|
}
|
|
3204
3353
|
} else if (prop === Symbol.toStringTag) {
|
|
3205
3354
|
return "FunctionReference";
|
|
@@ -3494,8 +3643,8 @@ var init_simple_client_node = __esm({
|
|
|
3494
3643
|
});
|
|
3495
3644
|
require_node_gyp_build = __commonJS({
|
|
3496
3645
|
"../node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports2, module2) {
|
|
3497
|
-
var
|
|
3498
|
-
var
|
|
3646
|
+
var fs11 = __require("fs");
|
|
3647
|
+
var path13 = __require("path");
|
|
3499
3648
|
var os8 = __require("os");
|
|
3500
3649
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
3501
3650
|
var vars2 = process.config && process.config.variables || {};
|
|
@@ -3512,21 +3661,21 @@ var init_simple_client_node = __esm({
|
|
|
3512
3661
|
return runtimeRequire(load.resolve(dir));
|
|
3513
3662
|
}
|
|
3514
3663
|
load.resolve = load.path = function(dir) {
|
|
3515
|
-
dir =
|
|
3664
|
+
dir = path13.resolve(dir || ".");
|
|
3516
3665
|
try {
|
|
3517
|
-
var name = runtimeRequire(
|
|
3666
|
+
var name = runtimeRequire(path13.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
3518
3667
|
if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
|
|
3519
3668
|
} catch (err) {
|
|
3520
3669
|
}
|
|
3521
3670
|
if (!prebuildsOnly) {
|
|
3522
|
-
var release = getFirst(
|
|
3671
|
+
var release = getFirst(path13.join(dir, "build/Release"), matchBuild);
|
|
3523
3672
|
if (release) return release;
|
|
3524
|
-
var debug = getFirst(
|
|
3673
|
+
var debug = getFirst(path13.join(dir, "build/Debug"), matchBuild);
|
|
3525
3674
|
if (debug) return debug;
|
|
3526
3675
|
}
|
|
3527
3676
|
var prebuild = resolve(dir);
|
|
3528
3677
|
if (prebuild) return prebuild;
|
|
3529
|
-
var nearby = resolve(
|
|
3678
|
+
var nearby = resolve(path13.dirname(process.execPath));
|
|
3530
3679
|
if (nearby) return nearby;
|
|
3531
3680
|
var target = [
|
|
3532
3681
|
"platform=" + platform,
|
|
@@ -3543,26 +3692,26 @@ var init_simple_client_node = __esm({
|
|
|
3543
3692
|
].filter(Boolean).join(" ");
|
|
3544
3693
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
3545
3694
|
function resolve(dir2) {
|
|
3546
|
-
var tuples = readdirSync(
|
|
3695
|
+
var tuples = readdirSync(path13.join(dir2, "prebuilds")).map(parseTuple);
|
|
3547
3696
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
3548
3697
|
if (!tuple) return;
|
|
3549
|
-
var prebuilds =
|
|
3698
|
+
var prebuilds = path13.join(dir2, "prebuilds", tuple.name);
|
|
3550
3699
|
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
3551
3700
|
var candidates = parsed.filter(matchTags(runtime, abi));
|
|
3552
3701
|
var winner = candidates.sort(compareTags(runtime))[0];
|
|
3553
|
-
if (winner) return
|
|
3702
|
+
if (winner) return path13.join(prebuilds, winner.file);
|
|
3554
3703
|
}
|
|
3555
3704
|
};
|
|
3556
3705
|
function readdirSync(dir) {
|
|
3557
3706
|
try {
|
|
3558
|
-
return
|
|
3707
|
+
return fs11.readdirSync(dir);
|
|
3559
3708
|
} catch (err) {
|
|
3560
3709
|
return [];
|
|
3561
3710
|
}
|
|
3562
3711
|
}
|
|
3563
3712
|
function getFirst(dir, filter) {
|
|
3564
3713
|
var files = readdirSync(dir).filter(filter);
|
|
3565
|
-
return files[0] &&
|
|
3714
|
+
return files[0] && path13.join(dir, files[0]);
|
|
3566
3715
|
}
|
|
3567
3716
|
function matchBuild(name) {
|
|
3568
3717
|
return /\.node$/.test(name);
|
|
@@ -3649,7 +3798,7 @@ var init_simple_client_node = __esm({
|
|
|
3649
3798
|
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
3650
3799
|
}
|
|
3651
3800
|
function isAlpine(platform2) {
|
|
3652
|
-
return platform2 === "linux" &&
|
|
3801
|
+
return platform2 === "linux" && fs11.existsSync("/etc/alpine-release");
|
|
3653
3802
|
}
|
|
3654
3803
|
load.parseTags = parseTags;
|
|
3655
3804
|
load.matchTags = matchTags;
|
|
@@ -7195,13 +7344,13 @@ var init_simple_client_node = __esm({
|
|
|
7195
7344
|
}
|
|
7196
7345
|
}
|
|
7197
7346
|
if (this.options.verifyClient) {
|
|
7198
|
-
const
|
|
7347
|
+
const info2 = {
|
|
7199
7348
|
origin: req.headers[`${version2 === 8 ? "sec-websocket-origin" : "origin"}`],
|
|
7200
7349
|
secure: !!(req.socket.authorized || req.socket.encrypted),
|
|
7201
7350
|
req
|
|
7202
7351
|
};
|
|
7203
7352
|
if (this.options.verifyClient.length === 2) {
|
|
7204
|
-
this.options.verifyClient(
|
|
7353
|
+
this.options.verifyClient(info2, (verified, code2, message, headers) => {
|
|
7205
7354
|
if (!verified) {
|
|
7206
7355
|
return abortHandshake(socket, code2 || 401, message, headers);
|
|
7207
7356
|
}
|
|
@@ -7217,7 +7366,7 @@ var init_simple_client_node = __esm({
|
|
|
7217
7366
|
});
|
|
7218
7367
|
return;
|
|
7219
7368
|
}
|
|
7220
|
-
if (!this.options.verifyClient(
|
|
7369
|
+
if (!this.options.verifyClient(info2)) return abortHandshake(socket, 401);
|
|
7221
7370
|
}
|
|
7222
7371
|
this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
|
|
7223
7372
|
}
|
|
@@ -8548,11 +8697,25 @@ var init_uploader = __esm({
|
|
|
8548
8697
|
throw err;
|
|
8549
8698
|
}
|
|
8550
8699
|
}
|
|
8551
|
-
/**
|
|
8552
|
-
async
|
|
8700
|
+
/** Wait for an in-flight incremental push to finish so a full sync isn't skipped by the guard. */
|
|
8701
|
+
async awaitIdle(timeoutMs = 15e3) {
|
|
8702
|
+
const deadline = Date.now() + timeoutMs;
|
|
8703
|
+
while (this.inFlight && Date.now() < deadline) await new Promise((r) => setTimeout(r, 100));
|
|
8704
|
+
}
|
|
8705
|
+
/**
|
|
8706
|
+
* Upload changed hour buckets and session summaries. Returns counts of pushed items.
|
|
8707
|
+
* With `full`, the record of what was already pushed is dropped first so *everything* is re-sent —
|
|
8708
|
+
* that is what recalibrates this device's totals on the dashboard after a parser or pricing change.
|
|
8709
|
+
*/
|
|
8710
|
+
async pushAll(buckets, sessions, sessionCosts, opts = {}) {
|
|
8711
|
+
if (opts.full) await this.awaitIdle();
|
|
8553
8712
|
if (this.inFlight) return { buckets: 0, sessions: 0 };
|
|
8554
8713
|
this.inFlight = true;
|
|
8555
8714
|
try {
|
|
8715
|
+
if (opts.full) {
|
|
8716
|
+
this.state.pushedBuckets = {};
|
|
8717
|
+
this.state.pushedSessions = {};
|
|
8718
|
+
}
|
|
8556
8719
|
const changed = [];
|
|
8557
8720
|
const hashes = /* @__PURE__ */ new Map();
|
|
8558
8721
|
for (const b of buckets.sort((a, b2) => a.hourStart - b2.hourStart)) {
|
|
@@ -8723,9 +8886,9 @@ var init_auth = __esm({
|
|
|
8723
8886
|
});
|
|
8724
8887
|
|
|
8725
8888
|
// src/cli.ts
|
|
8726
|
-
var
|
|
8727
|
-
var
|
|
8728
|
-
var
|
|
8889
|
+
var import_node_path12 = __toESM(require("node:path"));
|
|
8890
|
+
var import_node_fs10 = __toESM(require("node:fs"));
|
|
8891
|
+
var import_node_child_process5 = require("node:child_process");
|
|
8729
8892
|
|
|
8730
8893
|
// src/core/electron-install.ts
|
|
8731
8894
|
var import_node_fs = __toESM(require("node:fs"));
|
|
@@ -9258,21 +9421,21 @@ function findSessionInfo(storage, sessionID) {
|
|
|
9258
9421
|
const key = `${storage}|${sessionID}`;
|
|
9259
9422
|
const cached = sessionCache.get(key);
|
|
9260
9423
|
if (cached && (cached.info || Date.now() - cached.at < 6e4)) return cached.info;
|
|
9261
|
-
let
|
|
9424
|
+
let info2 = null;
|
|
9262
9425
|
const sessionDir = import_node_path7.default.join(storage, "session");
|
|
9263
9426
|
const direct = import_node_path7.default.join(sessionDir, "info", `${sessionID}.json`);
|
|
9264
|
-
if (import_node_fs5.default.existsSync(direct))
|
|
9265
|
-
if (!
|
|
9427
|
+
if (import_node_fs5.default.existsSync(direct)) info2 = readJsonFile(direct);
|
|
9428
|
+
if (!info2) {
|
|
9266
9429
|
for (const project of listDirs(sessionDir)) {
|
|
9267
9430
|
const p = import_node_path7.default.join(sessionDir, project, `${sessionID}.json`);
|
|
9268
9431
|
if (import_node_fs5.default.existsSync(p)) {
|
|
9269
|
-
|
|
9432
|
+
info2 = readJsonFile(p);
|
|
9270
9433
|
break;
|
|
9271
9434
|
}
|
|
9272
9435
|
}
|
|
9273
9436
|
}
|
|
9274
|
-
sessionCache.set(key, { at: Date.now(), info });
|
|
9275
|
-
return
|
|
9437
|
+
sessionCache.set(key, { at: Date.now(), info: info2 });
|
|
9438
|
+
return info2;
|
|
9276
9439
|
}
|
|
9277
9440
|
function openaiIsOAuth(dataDir) {
|
|
9278
9441
|
const file = import_node_path7.default.join(dataDir, "auth.json");
|
|
@@ -9342,16 +9505,16 @@ var opencodeSource = {
|
|
|
9342
9505
|
provider,
|
|
9343
9506
|
usage: { input, cached, cacheWrite, output, reasoning: t2.reasoning ?? 0, total: input + output, requests: 1 }
|
|
9344
9507
|
};
|
|
9345
|
-
const
|
|
9346
|
-
const cwd =
|
|
9508
|
+
const info2 = storage ? findSessionInfo(storage, m.sessionID) : null;
|
|
9509
|
+
const cwd = info2?.directory ?? null;
|
|
9347
9510
|
return {
|
|
9348
9511
|
sessionId: m.sessionID,
|
|
9349
9512
|
agent: file.root.agent,
|
|
9350
9513
|
provider,
|
|
9351
|
-
startedAt:
|
|
9514
|
+
startedAt: info2?.time?.created ?? ts2,
|
|
9352
9515
|
lastActivityAt: ts2,
|
|
9353
9516
|
cwd,
|
|
9354
|
-
projectName: projectNameOf(cwd) ??
|
|
9517
|
+
projectName: projectNameOf(cwd) ?? info2?.title ?? null,
|
|
9355
9518
|
originator: "opencode",
|
|
9356
9519
|
source: "opencode",
|
|
9357
9520
|
cliVersion: null,
|
|
@@ -9626,6 +9789,15 @@ var SessionStore = class {
|
|
|
9626
9789
|
onChange = null;
|
|
9627
9790
|
debounceTimer = null;
|
|
9628
9791
|
sessionCache = null;
|
|
9792
|
+
/**
|
|
9793
|
+
* Forget the parsed-file index so the next deep refresh re-reads and re-parses every transcript.
|
|
9794
|
+
* Used by the full sync: files are otherwise skipped while their size and mtime are unchanged, which
|
|
9795
|
+
* would keep stale numbers around after a parser or pricing change.
|
|
9796
|
+
*/
|
|
9797
|
+
reset() {
|
|
9798
|
+
this.files.clear();
|
|
9799
|
+
this.sessionCache = null;
|
|
9800
|
+
}
|
|
9629
9801
|
async refreshDeep() {
|
|
9630
9802
|
const o = this.getOptions();
|
|
9631
9803
|
this.roots = discoverSessionRoots({ extraSessionDirs: o.extraSessionDirs, sources: o.sources });
|
|
@@ -9784,7 +9956,20 @@ var SessionStore = class {
|
|
|
9784
9956
|
// src/core/stats.ts
|
|
9785
9957
|
init_src();
|
|
9786
9958
|
var LIVE_WINDOW_MS = 5 * 60 * 1e3;
|
|
9959
|
+
var RATE_WINDOW_MS = 6e4;
|
|
9960
|
+
var BURST_WINDOW_MS = 1e4;
|
|
9787
9961
|
var DAY3 = 864e5;
|
|
9962
|
+
function outputRate(events, now, windowMs, sessionStart) {
|
|
9963
|
+
const from = now - windowMs;
|
|
9964
|
+
let output = 0;
|
|
9965
|
+
for (const e of events) {
|
|
9966
|
+
if (e.ts <= from) continue;
|
|
9967
|
+
output += e.usage.output;
|
|
9968
|
+
}
|
|
9969
|
+
if (output <= 0) return 0;
|
|
9970
|
+
const elapsedSec = Math.max(1, (now - Math.max(from, sessionStart)) / 1e3);
|
|
9971
|
+
return output / elapsedSec;
|
|
9972
|
+
}
|
|
9788
9973
|
var PriceCache = class {
|
|
9789
9974
|
constructor(overrides) {
|
|
9790
9975
|
this.overrides = overrides;
|
|
@@ -9861,7 +10046,9 @@ function agentStats(sessions, since, prices) {
|
|
|
9861
10046
|
function computeStats(input) {
|
|
9862
10047
|
const now = input.now ?? Date.now();
|
|
9863
10048
|
const prices = new PriceCache(input.pricing);
|
|
9864
|
-
const sessions = input.sessions
|
|
10049
|
+
const sessions = input.sessions.map(
|
|
10050
|
+
(s) => s.events.every((e) => isOpenAIModel(e.model)) ? s : { ...s, events: s.events.filter((e) => isOpenAIModel(e.model)) }
|
|
10051
|
+
);
|
|
9865
10052
|
const allEvents = [];
|
|
9866
10053
|
const sessionCosts = /* @__PURE__ */ new Map();
|
|
9867
10054
|
let lastActivityAt = null;
|
|
@@ -9887,14 +10074,8 @@ function computeStats(input) {
|
|
|
9887
10074
|
let live = null;
|
|
9888
10075
|
const liveSession = sessions.filter((s) => s.events.length && now - s.lastActivityAt <= LIVE_WINDOW_MS).sort((a, b) => b.lastActivityAt - a.lastActivityAt)[0];
|
|
9889
10076
|
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
10077
|
const last = liveSession.events[liveSession.events.length - 1];
|
|
10078
|
+
const start = liveSession.startedAt || liveSession.events[0].ts;
|
|
9898
10079
|
live = {
|
|
9899
10080
|
sessionId: liveSession.sessionId,
|
|
9900
10081
|
agent: liveSession.agent,
|
|
@@ -9902,8 +10083,8 @@ function computeStats(input) {
|
|
|
9902
10083
|
model: liveSession.model,
|
|
9903
10084
|
startedAt: liveSession.startedAt,
|
|
9904
10085
|
lastEventAt: last.ts,
|
|
9905
|
-
tokensPerSecond:
|
|
9906
|
-
tokensPerSecond10s:
|
|
10086
|
+
tokensPerSecond: outputRate(liveSession.events, now, RATE_WINDOW_MS, start),
|
|
10087
|
+
tokensPerSecond10s: outputRate(liveSession.events, now, BURST_WINDOW_MS, start),
|
|
9907
10088
|
contextUsed: last.usage.input + last.usage.output,
|
|
9908
10089
|
contextWindow: liveSession.contextWindow,
|
|
9909
10090
|
sessionUsage: { ...liveSession.cumulative },
|
|
@@ -9930,6 +10111,7 @@ function computeStats(input) {
|
|
|
9930
10111
|
}
|
|
9931
10112
|
return {
|
|
9932
10113
|
buckets,
|
|
10114
|
+
sessions: sessions.filter((s) => s.events.length),
|
|
9933
10115
|
today,
|
|
9934
10116
|
week,
|
|
9935
10117
|
month,
|
|
@@ -10010,16 +10192,180 @@ async function fetchLiveRateLimits(appVersion, timeoutMs = 1e4) {
|
|
|
10010
10192
|
// src/core/engine.ts
|
|
10011
10193
|
init_i18n();
|
|
10012
10194
|
|
|
10013
|
-
// src/
|
|
10014
|
-
var
|
|
10195
|
+
// src/core/update.ts
|
|
10196
|
+
var import_node_fs9 = __toESM(require("node:fs"));
|
|
10197
|
+
var import_node_path11 = __toESM(require("node:path"));
|
|
10198
|
+
var import_node_child_process4 = require("node:child_process");
|
|
10199
|
+
init_config();
|
|
10200
|
+
init_version();
|
|
10201
|
+
var NPM_PACKAGE = "codex-token-tracker";
|
|
10202
|
+
var CHECK_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
10203
|
+
var REQUEST_TIMEOUT_MS = 8e3;
|
|
10204
|
+
var INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
10205
|
+
var LOG_TAIL_CHARS = 4e3;
|
|
10206
|
+
function parseVersion(v2) {
|
|
10207
|
+
const s = String(v2 ?? "").trim().replace(/^v/, "");
|
|
10208
|
+
const i2 = s.indexOf("-");
|
|
10209
|
+
const core = i2 === -1 ? s : s.slice(0, i2);
|
|
10210
|
+
const pre = i2 === -1 ? "" : s.slice(i2 + 1);
|
|
10211
|
+
const n = core.split(".").map((x) => Number.parseInt(x, 10) || 0);
|
|
10212
|
+
return { nums: [n[0] ?? 0, n[1] ?? 0, n[2] ?? 0], pre };
|
|
10213
|
+
}
|
|
10214
|
+
function compareVersions(a, b) {
|
|
10215
|
+
const A = parseVersion(a);
|
|
10216
|
+
const B = parseVersion(b);
|
|
10217
|
+
for (let i2 = 0; i2 < 3; i2++) if (A.nums[i2] !== B.nums[i2]) return A.nums[i2] < B.nums[i2] ? -1 : 1;
|
|
10218
|
+
if (A.pre === B.pre) return 0;
|
|
10219
|
+
if (!A.pre) return 1;
|
|
10220
|
+
if (!B.pre) return -1;
|
|
10221
|
+
return A.pre < B.pre ? -1 : 1;
|
|
10222
|
+
}
|
|
10223
|
+
function isDevBuild(version2) {
|
|
10224
|
+
return IS_DEV_BUILD || version2.startsWith("0.0.0");
|
|
10225
|
+
}
|
|
10226
|
+
function registryBase() {
|
|
10227
|
+
const r = process.env.CODEX_TRACKER_REGISTRY || process.env.npm_config_registry || process.env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org";
|
|
10228
|
+
return r.replace(/\/+$/, "");
|
|
10229
|
+
}
|
|
10230
|
+
async function fetchLatestVersion(signal) {
|
|
10231
|
+
const ctrl = new AbortController();
|
|
10232
|
+
const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
|
|
10233
|
+
signal?.addEventListener("abort", () => ctrl.abort(), { once: true });
|
|
10234
|
+
try {
|
|
10235
|
+
const res = await fetch(`${registryBase()}/-/package/${NPM_PACKAGE}/dist-tags`, {
|
|
10236
|
+
signal: ctrl.signal,
|
|
10237
|
+
headers: { accept: "application/json" }
|
|
10238
|
+
});
|
|
10239
|
+
if (!res.ok) throw new Error(`registry responded ${res.status}`);
|
|
10240
|
+
const json = await res.json();
|
|
10241
|
+
const latest = json?.latest;
|
|
10242
|
+
if (typeof latest !== "string" || !latest) throw new Error("registry returned no `latest` tag");
|
|
10243
|
+
return latest;
|
|
10244
|
+
} finally {
|
|
10245
|
+
clearTimeout(timer);
|
|
10246
|
+
}
|
|
10247
|
+
}
|
|
10248
|
+
function detectPackageManager(installDir = import_node_path11.default.resolve(__dirname, "..")) {
|
|
10249
|
+
const p = installDir.replace(/\\/g, "/").toLowerCase();
|
|
10250
|
+
if (/\/\.?bun\//.test(p)) return "bun";
|
|
10251
|
+
if (/\/\.?pnpm[/-]/.test(p)) return "pnpm";
|
|
10252
|
+
if (/\/\.?yarn\//.test(p)) return "yarn";
|
|
10253
|
+
const ua = (process.env.npm_config_user_agent ?? "").toLowerCase();
|
|
10254
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
10255
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
10256
|
+
if (ua.startsWith("bun")) return "bun";
|
|
10257
|
+
return "npm";
|
|
10258
|
+
}
|
|
10259
|
+
function updateArgs(pm, spec = `${NPM_PACKAGE}@latest`) {
|
|
10260
|
+
switch (pm) {
|
|
10261
|
+
case "pnpm":
|
|
10262
|
+
return ["add", "-g", spec];
|
|
10263
|
+
case "yarn":
|
|
10264
|
+
return ["global", "add", spec];
|
|
10265
|
+
case "bun":
|
|
10266
|
+
return ["add", "-g", spec];
|
|
10267
|
+
default:
|
|
10268
|
+
return ["install", "-g", spec];
|
|
10269
|
+
}
|
|
10270
|
+
}
|
|
10271
|
+
function updateCommand(pm, spec) {
|
|
10272
|
+
return `${pm} ${updateArgs(pm, spec).join(" ")}`;
|
|
10273
|
+
}
|
|
10274
|
+
function cachePath() {
|
|
10275
|
+
return import_node_path11.default.join(configDir(), "update.json");
|
|
10276
|
+
}
|
|
10277
|
+
function readCache() {
|
|
10278
|
+
try {
|
|
10279
|
+
const raw = JSON.parse(import_node_fs9.default.readFileSync(cachePath(), "utf8"));
|
|
10280
|
+
if (typeof raw?.latest === "string" && typeof raw.checkedAt === "number") {
|
|
10281
|
+
return { latest: raw.latest, checkedAt: raw.checkedAt };
|
|
10282
|
+
}
|
|
10283
|
+
} catch {
|
|
10284
|
+
}
|
|
10285
|
+
return null;
|
|
10286
|
+
}
|
|
10287
|
+
function writeCache(c) {
|
|
10288
|
+
try {
|
|
10289
|
+
import_node_fs9.default.mkdirSync(import_node_path11.default.dirname(cachePath()), { recursive: true });
|
|
10290
|
+
import_node_fs9.default.writeFileSync(cachePath(), JSON.stringify(c, null, 2));
|
|
10291
|
+
} catch {
|
|
10292
|
+
}
|
|
10293
|
+
}
|
|
10294
|
+
function info(current, latest, checkedAt, error) {
|
|
10295
|
+
const pm = detectPackageManager();
|
|
10296
|
+
return {
|
|
10297
|
+
current,
|
|
10298
|
+
latest,
|
|
10299
|
+
available: Boolean(latest) && !isDevBuild(current) && compareVersions(latest, current) > 0,
|
|
10300
|
+
checkedAt,
|
|
10301
|
+
error,
|
|
10302
|
+
packageManager: pm,
|
|
10303
|
+
command: updateCommand(pm)
|
|
10304
|
+
};
|
|
10305
|
+
}
|
|
10306
|
+
async function checkForUpdate(opts = {}) {
|
|
10307
|
+
const current = opts.current ?? APP_VERSION;
|
|
10308
|
+
const cached = readCache();
|
|
10309
|
+
if (!opts.force && cached && Date.now() - cached.checkedAt < CHECK_TTL_MS) {
|
|
10310
|
+
return info(current, cached.latest, cached.checkedAt, null);
|
|
10311
|
+
}
|
|
10312
|
+
try {
|
|
10313
|
+
const latest = await fetchLatestVersion(opts.signal);
|
|
10314
|
+
const checkedAt = Date.now();
|
|
10315
|
+
writeCache({ latest, checkedAt });
|
|
10316
|
+
return info(current, latest, checkedAt, null);
|
|
10317
|
+
} catch (err) {
|
|
10318
|
+
return info(current, cached?.latest ?? null, cached?.checkedAt ?? null, err.message);
|
|
10319
|
+
}
|
|
10320
|
+
}
|
|
10321
|
+
function runUpdate(opts = {}) {
|
|
10322
|
+
const pm = detectPackageManager();
|
|
10323
|
+
const spec = opts.version ? `${NPM_PACKAGE}@${opts.version}` : `${NPM_PACKAGE}@latest`;
|
|
10324
|
+
const args = updateArgs(pm, spec);
|
|
10325
|
+
const command = `${pm} ${args.join(" ")}`;
|
|
10326
|
+
return new Promise((resolve) => {
|
|
10327
|
+
let output = "";
|
|
10328
|
+
const collect = (chunk) => {
|
|
10329
|
+
const text = chunk.toString();
|
|
10330
|
+
opts.onOutput?.(text);
|
|
10331
|
+
output = (output + text).slice(-LOG_TAIL_CHARS);
|
|
10332
|
+
};
|
|
10333
|
+
let child;
|
|
10334
|
+
try {
|
|
10335
|
+
child = (0, import_node_child_process4.spawn)(pm, args, {
|
|
10336
|
+
// Electron's bundled Node has no shell PATH resolution for `npm.cmd` on Windows.
|
|
10337
|
+
shell: process.platform === "win32",
|
|
10338
|
+
env: { ...process.env, ELECTRON_RUN_AS_NODE: void 0 },
|
|
10339
|
+
windowsHide: true
|
|
10340
|
+
});
|
|
10341
|
+
} catch (err) {
|
|
10342
|
+
resolve({ ok: false, code: null, command, output: err.message });
|
|
10343
|
+
return;
|
|
10344
|
+
}
|
|
10345
|
+
const timer = setTimeout(() => child.kill(), INSTALL_TIMEOUT_MS);
|
|
10346
|
+
child.stdout?.on("data", collect);
|
|
10347
|
+
child.stderr?.on("data", collect);
|
|
10348
|
+
child.on("error", (err) => {
|
|
10349
|
+
clearTimeout(timer);
|
|
10350
|
+
resolve({ ok: false, code: null, command, output: output + err.message });
|
|
10351
|
+
});
|
|
10352
|
+
child.on("close", (code2) => {
|
|
10353
|
+
clearTimeout(timer);
|
|
10354
|
+
resolve({ ok: code2 === 0, code: code2, command, output });
|
|
10355
|
+
});
|
|
10356
|
+
});
|
|
10357
|
+
}
|
|
10015
10358
|
|
|
10016
10359
|
// src/core/engine.ts
|
|
10360
|
+
init_version();
|
|
10017
10361
|
var SHALLOW_MS = 3e3;
|
|
10018
10362
|
var DEEP_MS = 6e4;
|
|
10019
10363
|
var TICK_MS = 2e3;
|
|
10020
10364
|
var REMOTE_MS = 6e4;
|
|
10021
10365
|
var LIVE_LIMITS_DEBOUNCE_MS = 1e4;
|
|
10022
10366
|
var LIVE_LIMITS_MIN_GAP_MS = 2e4;
|
|
10367
|
+
var UPDATE_CHECK_MS = 6 * 60 * 60 * 1e3;
|
|
10368
|
+
var SYNC_BANNER_MS = 25e3;
|
|
10023
10369
|
var Engine = class extends import_node_events.EventEmitter {
|
|
10024
10370
|
constructor(opts) {
|
|
10025
10371
|
super();
|
|
@@ -10056,6 +10402,16 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10056
10402
|
liveLimitsInFlight = false;
|
|
10057
10403
|
liveLimitsTimer = null;
|
|
10058
10404
|
lastLiveLimitsAttempt = 0;
|
|
10405
|
+
update = null;
|
|
10406
|
+
updateStatus = "idle";
|
|
10407
|
+
updateLog = null;
|
|
10408
|
+
syncStatus = "idle";
|
|
10409
|
+
syncPhase = null;
|
|
10410
|
+
syncStartedAt = null;
|
|
10411
|
+
syncFinishedAt = null;
|
|
10412
|
+
syncError = null;
|
|
10413
|
+
syncResult = null;
|
|
10414
|
+
syncBannerTimer = null;
|
|
10059
10415
|
get heatmapWeeks() {
|
|
10060
10416
|
return this.opts.heatmapWeeks ?? 16;
|
|
10061
10417
|
}
|
|
@@ -10077,6 +10433,8 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10077
10433
|
return;
|
|
10078
10434
|
}
|
|
10079
10435
|
void this.refreshLiveLimits(true);
|
|
10436
|
+
void this.checkUpdate(false);
|
|
10437
|
+
this.timers.push(setInterval(() => void this.checkUpdate(false), UPDATE_CHECK_MS));
|
|
10080
10438
|
this.store.startWatching(() => void this.refresh(false));
|
|
10081
10439
|
this.timers.push(setInterval(() => void this.refresh(false), SHALLOW_MS));
|
|
10082
10440
|
this.timers.push(setInterval(() => void this.refresh(true), DEEP_MS));
|
|
@@ -10104,6 +10462,8 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10104
10462
|
this.timers = [];
|
|
10105
10463
|
if (this.liveLimitsTimer) clearTimeout(this.liveLimitsTimer);
|
|
10106
10464
|
this.liveLimitsTimer = null;
|
|
10465
|
+
if (this.syncBannerTimer) clearTimeout(this.syncBannerTimer);
|
|
10466
|
+
this.syncBannerTimer = null;
|
|
10107
10467
|
this.store.stopWatching();
|
|
10108
10468
|
this.pending?.abort.abort();
|
|
10109
10469
|
}
|
|
@@ -10183,7 +10543,7 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10183
10543
|
if (!this.signedIn) return { buckets: 0, sessions: 0 };
|
|
10184
10544
|
if (!this.stats) this.recompute();
|
|
10185
10545
|
try {
|
|
10186
|
-
const r = await this.uploader.pushAll(this.stats.buckets, this.
|
|
10546
|
+
const r = await this.uploader.pushAll(this.stats.buckets, this.stats.sessions, this.stats.sessionCosts);
|
|
10187
10547
|
this.emitSnapshot();
|
|
10188
10548
|
return r;
|
|
10189
10549
|
} catch (err) {
|
|
@@ -10207,6 +10567,154 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10207
10567
|
await this.uploader.fetchRemote(this.heatmapWeeks);
|
|
10208
10568
|
this.recompute();
|
|
10209
10569
|
}
|
|
10570
|
+
/**
|
|
10571
|
+
* Full sync ("calibrate this device"). Unlike the periodic incremental upload this deliberately
|
|
10572
|
+
* throws away every cache on the way:
|
|
10573
|
+
* 1. re-read the config so sources enabled since start-up are picked up,
|
|
10574
|
+
* 2. drop the parsed-file index and re-discover + re-parse every transcript of every agent
|
|
10575
|
+
* (Codex plus the agents running on the Codex subscription: pi, OpenCode, Cline/Roo/Kilo,
|
|
10576
|
+
* Hermes and any custom `extraSessionDirs`),
|
|
10577
|
+
* 3. recompute the aggregates with the current pricing table,
|
|
10578
|
+
* 4. re-upload *everything* — not just what changed — so the dashboard's totals for this device
|
|
10579
|
+
* are replaced by the freshly computed ones,
|
|
10580
|
+
* 5. pull the other devices' rows and the live rate limits back down.
|
|
10581
|
+
*
|
|
10582
|
+
* Returns null when a sync is already running. Never throws: failures land in the returned state.
|
|
10583
|
+
*/
|
|
10584
|
+
async syncNow() {
|
|
10585
|
+
if (this.syncStatus === "running") return null;
|
|
10586
|
+
if (this.syncBannerTimer) {
|
|
10587
|
+
clearTimeout(this.syncBannerTimer);
|
|
10588
|
+
this.syncBannerTimer = null;
|
|
10589
|
+
}
|
|
10590
|
+
const startedAt = Date.now();
|
|
10591
|
+
this.syncStatus = "running";
|
|
10592
|
+
this.syncPhase = null;
|
|
10593
|
+
this.syncStartedAt = startedAt;
|
|
10594
|
+
this.syncFinishedAt = null;
|
|
10595
|
+
this.syncError = null;
|
|
10596
|
+
const phase = (p) => {
|
|
10597
|
+
this.syncPhase = p;
|
|
10598
|
+
this.emitSnapshot();
|
|
10599
|
+
};
|
|
10600
|
+
try {
|
|
10601
|
+
phase("scanning");
|
|
10602
|
+
this.reloadConfig();
|
|
10603
|
+
this.store.reset();
|
|
10604
|
+
await this.store.refreshDeep();
|
|
10605
|
+
phase("computing");
|
|
10606
|
+
this.recompute();
|
|
10607
|
+
let uploadedBuckets = 0;
|
|
10608
|
+
let uploadedSessions = 0;
|
|
10609
|
+
const uploaded = this.opts.upload && this.signedIn;
|
|
10610
|
+
if (uploaded) {
|
|
10611
|
+
phase("uploading");
|
|
10612
|
+
const r = await this.uploader.pushAll(this.stats.buckets, this.stats.sessions, this.stats.sessionCosts, { full: true });
|
|
10613
|
+
uploadedBuckets = r.buckets;
|
|
10614
|
+
uploadedSessions = r.sessions;
|
|
10615
|
+
await this.heartbeatNow().catch(() => {
|
|
10616
|
+
});
|
|
10617
|
+
phase("downloading");
|
|
10618
|
+
await this.fetchRemoteNow();
|
|
10619
|
+
}
|
|
10620
|
+
phase("limits");
|
|
10621
|
+
await this.refreshLiveLimits(true);
|
|
10622
|
+
const finishedAt = Date.now();
|
|
10623
|
+
this.syncResult = {
|
|
10624
|
+
startedAt,
|
|
10625
|
+
finishedAt,
|
|
10626
|
+
durationMs: finishedAt - startedAt,
|
|
10627
|
+
files: this.store.fileCount,
|
|
10628
|
+
sessions: this.store.sessions().length,
|
|
10629
|
+
roots: this.store.roots.length,
|
|
10630
|
+
agents: [...new Set(this.store.roots.map((r) => r.agent))].sort(),
|
|
10631
|
+
uploadedBuckets,
|
|
10632
|
+
uploadedSessions,
|
|
10633
|
+
uploaded
|
|
10634
|
+
};
|
|
10635
|
+
this.syncStatus = "done";
|
|
10636
|
+
this.syncFinishedAt = finishedAt;
|
|
10637
|
+
this.opts.log?.(
|
|
10638
|
+
`sync: ${this.syncResult.files} files, ${this.syncResult.sessions} sessions, ${uploadedBuckets} buckets / ${uploadedSessions} sessions uploaded in ${this.syncResult.durationMs} ms`
|
|
10639
|
+
);
|
|
10640
|
+
return this.syncResult;
|
|
10641
|
+
} catch (err) {
|
|
10642
|
+
this.syncStatus = "error";
|
|
10643
|
+
this.syncFinishedAt = Date.now();
|
|
10644
|
+
this.syncError = err instanceof SignedOutError ? "not signed in" : errorMessage(err);
|
|
10645
|
+
this.opts.log?.(`sync failed: ${this.syncError}`);
|
|
10646
|
+
return null;
|
|
10647
|
+
} finally {
|
|
10648
|
+
this.syncPhase = null;
|
|
10649
|
+
this.emitSnapshot();
|
|
10650
|
+
this.scheduleSyncBannerClear();
|
|
10651
|
+
}
|
|
10652
|
+
}
|
|
10653
|
+
/** Return the sync banner to "idle" a little after it finished, keeping `last` for the footer. */
|
|
10654
|
+
scheduleSyncBannerClear() {
|
|
10655
|
+
if (this.opts.watch === false) return;
|
|
10656
|
+
if (this.syncBannerTimer) clearTimeout(this.syncBannerTimer);
|
|
10657
|
+
this.syncBannerTimer = setTimeout(() => {
|
|
10658
|
+
this.syncBannerTimer = null;
|
|
10659
|
+
if (this.syncStatus === "done" || this.syncStatus === "error") {
|
|
10660
|
+
this.syncStatus = "idle";
|
|
10661
|
+
this.emitSnapshot();
|
|
10662
|
+
}
|
|
10663
|
+
}, SYNC_BANNER_MS);
|
|
10664
|
+
this.syncBannerTimer.unref?.();
|
|
10665
|
+
}
|
|
10666
|
+
syncState() {
|
|
10667
|
+
return {
|
|
10668
|
+
status: this.syncStatus,
|
|
10669
|
+
phase: this.syncPhase,
|
|
10670
|
+
startedAt: this.syncStartedAt,
|
|
10671
|
+
finishedAt: this.syncFinishedAt,
|
|
10672
|
+
error: this.syncError,
|
|
10673
|
+
last: this.syncResult
|
|
10674
|
+
};
|
|
10675
|
+
}
|
|
10676
|
+
/**
|
|
10677
|
+
* Ask the npm registry for the newest published version (cached for 6 h unless `force`).
|
|
10678
|
+
* Best-effort: a failure is recorded on the snapshot, never thrown.
|
|
10679
|
+
*/
|
|
10680
|
+
async checkUpdate(force) {
|
|
10681
|
+
if (!this.config.checkUpdates) {
|
|
10682
|
+
this.update = null;
|
|
10683
|
+
return null;
|
|
10684
|
+
}
|
|
10685
|
+
if (this.updateStatus === "checking" || this.updateStatus === "installing") return this.update;
|
|
10686
|
+
this.updateStatus = "checking";
|
|
10687
|
+
this.emitSnapshot();
|
|
10688
|
+
this.update = await checkForUpdate({ force });
|
|
10689
|
+
this.updateStatus = "idle";
|
|
10690
|
+
if (this.update.error) this.opts.log?.(`update check failed: ${this.update.error}`);
|
|
10691
|
+
this.emitSnapshot();
|
|
10692
|
+
return this.update;
|
|
10693
|
+
}
|
|
10694
|
+
/**
|
|
10695
|
+
* Install the newest version globally with the package manager this copy came from.
|
|
10696
|
+
* The new code only takes effect once the app is restarted, so the UI says so rather than
|
|
10697
|
+
* pretending to hot-swap itself.
|
|
10698
|
+
*/
|
|
10699
|
+
async installUpdate() {
|
|
10700
|
+
if (this.updateStatus === "installing") return false;
|
|
10701
|
+
if (!this.update?.available) await this.checkUpdate(true);
|
|
10702
|
+
if (!this.update?.available) return false;
|
|
10703
|
+
this.updateStatus = "installing";
|
|
10704
|
+
this.updateLog = null;
|
|
10705
|
+
this.emitSnapshot();
|
|
10706
|
+
const r = await runUpdate({ version: this.update.latest ?? void 0 });
|
|
10707
|
+
this.updateStatus = r.ok ? "installed" : "failed";
|
|
10708
|
+
this.updateLog = r.ok ? null : `${r.command}
|
|
10709
|
+
${r.output}`.trim();
|
|
10710
|
+
if (!r.ok) this.opts.log?.(`update install failed (${r.code}): ${r.output}`);
|
|
10711
|
+
this.emitSnapshot();
|
|
10712
|
+
return r.ok;
|
|
10713
|
+
}
|
|
10714
|
+
updateState() {
|
|
10715
|
+
if (!this.config.checkUpdates || !this.update) return null;
|
|
10716
|
+
return { ...this.update, status: this.updateStatus, log: this.updateLog };
|
|
10717
|
+
}
|
|
10210
10718
|
/** Start the device-code login flow (resolves when approved / denied / expired / cancelled). */
|
|
10211
10719
|
async login(openBrowser, onCode) {
|
|
10212
10720
|
if (this.pending) return { status: "cancelled" };
|
|
@@ -10282,6 +10790,7 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10282
10790
|
const sessions = this.store.sessions();
|
|
10283
10791
|
return {
|
|
10284
10792
|
version: APP_VERSION,
|
|
10793
|
+
channel: APP_CHANNEL,
|
|
10285
10794
|
generatedAt: Date.now(),
|
|
10286
10795
|
language: this.language(),
|
|
10287
10796
|
languageSetting: this.config.language,
|
|
@@ -10299,6 +10808,8 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10299
10808
|
rateLimits: this.liveLimits ?? fromLogRateLimits(s.logRateLimits),
|
|
10300
10809
|
rateLimitsError: this.config.liveRateLimits ? this.liveLimitsError : null,
|
|
10301
10810
|
rateLimitsUpdatedAt: this.liveLimits ? this.liveLimitsAt : s.logRateLimits?.observedAt ?? null,
|
|
10811
|
+
update: this.updateState(),
|
|
10812
|
+
sync: this.syncState(),
|
|
10302
10813
|
modelsToday: s.modelsToday,
|
|
10303
10814
|
modelsMonth: s.modelsMonth,
|
|
10304
10815
|
byAgentToday: s.byAgentToday,
|
|
@@ -10315,6 +10826,7 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10315
10826
|
},
|
|
10316
10827
|
sessionDirs: SessionStore.rootDirs(this.store.roots),
|
|
10317
10828
|
sessionRoots: this.store.roots.map((r) => ({ dir: r.dir, agent: r.agent, format: r.format, origin: r.origin })),
|
|
10829
|
+
configDir: configDir(),
|
|
10318
10830
|
launchAtLogin: this.config.launchAtLogin,
|
|
10319
10831
|
trayTitle: this.config.trayTitle
|
|
10320
10832
|
};
|
|
@@ -10326,6 +10838,7 @@ init_platform();
|
|
|
10326
10838
|
init_config();
|
|
10327
10839
|
init_uploader();
|
|
10328
10840
|
init_i18n();
|
|
10841
|
+
init_version();
|
|
10329
10842
|
function parseArgs2(argv) {
|
|
10330
10843
|
const out = { command: null, positional: [], flags: {} };
|
|
10331
10844
|
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
@@ -10357,14 +10870,14 @@ function applyDashboardFlag(flags) {
|
|
|
10357
10870
|
function electronBinary() {
|
|
10358
10871
|
try {
|
|
10359
10872
|
const p = require("electron");
|
|
10360
|
-
return typeof p === "string" && p &&
|
|
10873
|
+
return typeof p === "string" && p && import_node_fs10.default.existsSync(p) ? p : null;
|
|
10361
10874
|
} catch {
|
|
10362
10875
|
return null;
|
|
10363
10876
|
}
|
|
10364
10877
|
}
|
|
10365
10878
|
function electronPackageDir() {
|
|
10366
10879
|
try {
|
|
10367
|
-
return
|
|
10880
|
+
return import_node_path12.default.dirname(require.resolve("electron/package.json"));
|
|
10368
10881
|
} catch {
|
|
10369
10882
|
return null;
|
|
10370
10883
|
}
|
|
@@ -10384,11 +10897,11 @@ async function ensureElectron() {
|
|
|
10384
10897
|
log2.error(` ${err.message}`);
|
|
10385
10898
|
}
|
|
10386
10899
|
if (!bin) {
|
|
10387
|
-
const installer =
|
|
10388
|
-
if (
|
|
10900
|
+
const installer = import_node_path12.default.join(dir, "install.js");
|
|
10901
|
+
if (import_node_fs10.default.existsSync(installer)) {
|
|
10389
10902
|
const env = { ...process.env };
|
|
10390
10903
|
delete env.ELECTRON_SKIP_BINARY_DOWNLOAD;
|
|
10391
|
-
(0,
|
|
10904
|
+
(0, import_node_child_process5.spawnSync)(process.execPath, [installer], { stdio: "inherit", cwd: dir, env });
|
|
10392
10905
|
}
|
|
10393
10906
|
}
|
|
10394
10907
|
try {
|
|
@@ -10408,9 +10921,9 @@ async function startMenubar(background) {
|
|
|
10408
10921
|
}
|
|
10409
10922
|
const env = { ...process.env, CODEX_TRACKER_HOME: configDir() };
|
|
10410
10923
|
delete env.ELECTRON_RUN_AS_NODE;
|
|
10411
|
-
const mainPath =
|
|
10924
|
+
const mainPath = import_node_path12.default.join(__dirname, "main.js");
|
|
10412
10925
|
console.log(t2("cliStartingMenubar"));
|
|
10413
|
-
const child = (0,
|
|
10926
|
+
const child = (0, import_node_child_process5.spawn)(bin, [mainPath], {
|
|
10414
10927
|
stdio: background ? "ignore" : "inherit",
|
|
10415
10928
|
detached: background,
|
|
10416
10929
|
env,
|
|
@@ -10491,8 +11004,43 @@ async function runStatus(json) {
|
|
|
10491
11004
|
s.auth.status === "signedIn" ? `${t2("signedInAs", { name: s.auth.user?.name || s.auth.user?.email || "?" })} \xB7 ${s.auth.dashboardUrl}` : t2("cliNotSignedIn")
|
|
10492
11005
|
);
|
|
10493
11006
|
line(t2("cliStatusDirs"), s.sessionDirs.join("\n" + " ".repeat(15)) || "-");
|
|
11007
|
+
if (IS_DEV_BUILD) console.log(t2("cliChannelDev", { url: s.auth.dashboardUrl, dir: configDir() }));
|
|
10494
11008
|
const byAgent = Object.entries(s.counts.byAgent).sort((a, b) => b[1].sessions - a[1].sessions).map(([agent, c]) => `${agent} ${c.sessions}`).join(", ");
|
|
10495
11009
|
console.log(t2("sessions", { n: s.counts.sessions }) + " \xB7 " + t2("files", { n: s.counts.files }) + (byAgent ? ` (${byAgent})` : ""));
|
|
11010
|
+
if (cfg.checkUpdates) {
|
|
11011
|
+
const u = await checkForUpdate();
|
|
11012
|
+
if (u.available) console.log("\n" + t2("cliUpdateAvailable", { current: u.current, latest: u.latest ?? "?" }) + ` \u2014 ${u.command}`);
|
|
11013
|
+
}
|
|
11014
|
+
return 0;
|
|
11015
|
+
}
|
|
11016
|
+
async function runSync(flags) {
|
|
11017
|
+
const cfg = applyDashboardFlag(flags);
|
|
11018
|
+
const L = lang(cfg);
|
|
11019
|
+
const t2 = makeT(L);
|
|
11020
|
+
const engine = new Engine({
|
|
11021
|
+
upload: true,
|
|
11022
|
+
watch: false,
|
|
11023
|
+
systemLocale: systemLocale(),
|
|
11024
|
+
log: process.env.CODEX_TRACKER_DEBUG ? (m) => console.error("[sync]", m) : void 0
|
|
11025
|
+
});
|
|
11026
|
+
console.log(t2("cliSyncStart"));
|
|
11027
|
+
const result = await engine.syncNow();
|
|
11028
|
+
if (!result) {
|
|
11029
|
+
console.error(t2("cliSyncFailed", { message: engine.snapshot().sync.error ?? "?" }));
|
|
11030
|
+
return 1;
|
|
11031
|
+
}
|
|
11032
|
+
console.log(t2("cliSyncScanned", { files: result.files, roots: result.roots, agents: result.agents.join(", ") || "-" }));
|
|
11033
|
+
if (result.uploaded) console.log(t2("cliSyncUploaded", { buckets: result.uploadedBuckets, sessions: result.uploadedSessions }));
|
|
11034
|
+
else console.log(t2("cliSyncLocal"));
|
|
11035
|
+
const s = engine.snapshot();
|
|
11036
|
+
console.log(
|
|
11037
|
+
t2("cliSyncDone", {
|
|
11038
|
+
seconds: (result.durationMs / 1e3).toFixed(1),
|
|
11039
|
+
sessions: result.sessions,
|
|
11040
|
+
tokens: formatTokens(s.today.usage.total),
|
|
11041
|
+
cost: formatUSD(s.today.cost)
|
|
11042
|
+
})
|
|
11043
|
+
);
|
|
10496
11044
|
return 0;
|
|
10497
11045
|
}
|
|
10498
11046
|
async function runLogin(flags) {
|
|
@@ -10598,6 +11146,7 @@ function runPaths() {
|
|
|
10598
11146
|
const off = SOURCE_IDS.filter((id) => !cfg.sources[id]);
|
|
10599
11147
|
if (off.length) console.log(` (disabled: ${off.join(", ")})`);
|
|
10600
11148
|
console.log(t2("cliConfigDir", { dir: configDir() }));
|
|
11149
|
+
if (IS_DEV_BUILD) console.log(t2("cliChannelDev", { url: cfg.dashboardUrl, dir: configDir() }));
|
|
10601
11150
|
return 0;
|
|
10602
11151
|
}
|
|
10603
11152
|
function runConfig(positional) {
|
|
@@ -10642,6 +11191,35 @@ function runConfig(positional) {
|
|
|
10642
11191
|
console.log(t2("cliConfigDir", { dir: configDir() }));
|
|
10643
11192
|
return 0;
|
|
10644
11193
|
}
|
|
11194
|
+
async function runUpdateCommand(flags) {
|
|
11195
|
+
const t2 = makeT(lang());
|
|
11196
|
+
if (IS_DEV_BUILD) {
|
|
11197
|
+
console.error(t2("cliUpdateDevBuild"));
|
|
11198
|
+
return 1;
|
|
11199
|
+
}
|
|
11200
|
+
const info2 = await checkForUpdate({ force: true });
|
|
11201
|
+
if (info2.error && !info2.latest) {
|
|
11202
|
+
console.error(t2("cliUpdateCheckFailed", { message: info2.error }));
|
|
11203
|
+
return 1;
|
|
11204
|
+
}
|
|
11205
|
+
if (!info2.available) {
|
|
11206
|
+
console.log(t2("cliUpdateLatest", { version: info2.current }));
|
|
11207
|
+
return 0;
|
|
11208
|
+
}
|
|
11209
|
+
console.log(t2("cliUpdateAvailable", { current: info2.current, latest: info2.latest ?? "?" }));
|
|
11210
|
+
if (flags.check === true) {
|
|
11211
|
+
console.log(` ${info2.command}`);
|
|
11212
|
+
return 0;
|
|
11213
|
+
}
|
|
11214
|
+
console.log(t2("cliUpdateRunning", { command: info2.command }));
|
|
11215
|
+
const r = await runUpdate({ version: info2.latest ?? void 0, onOutput: (c) => process.stdout.write(c) });
|
|
11216
|
+
if (!r.ok) {
|
|
11217
|
+
console.error(t2("cliUpdateFailed", { code: r.code ?? "?", command: r.command }));
|
|
11218
|
+
return 1;
|
|
11219
|
+
}
|
|
11220
|
+
console.log(t2("cliUpdateDone", { version: info2.latest ?? "?" }));
|
|
11221
|
+
return 0;
|
|
11222
|
+
}
|
|
10645
11223
|
function runLang(positional) {
|
|
10646
11224
|
const value = positional[0];
|
|
10647
11225
|
if (!value || !["en", "zh", "auto"].includes(value)) {
|
|
@@ -10657,6 +11235,7 @@ async function main() {
|
|
|
10657
11235
|
const t2 = makeT(lang());
|
|
10658
11236
|
if (args.flags.version) {
|
|
10659
11237
|
console.log(t2("cliVersion", { version: APP_VERSION }));
|
|
11238
|
+
if (IS_DEV_BUILD) console.log(t2("cliChannelDev", { url: loadConfig().dashboardUrl, dir: configDir() }));
|
|
10660
11239
|
return 0;
|
|
10661
11240
|
}
|
|
10662
11241
|
if (args.flags.help || args.command === "help") {
|
|
@@ -10693,12 +11272,17 @@ async function main() {
|
|
|
10693
11272
|
}
|
|
10694
11273
|
case "status":
|
|
10695
11274
|
return runStatus(args.flags.json === true);
|
|
11275
|
+
case "sync":
|
|
11276
|
+
return runSync(args.flags);
|
|
10696
11277
|
case "paths":
|
|
10697
11278
|
return runPaths();
|
|
10698
11279
|
case "config":
|
|
10699
11280
|
return runConfig(args.positional);
|
|
10700
11281
|
case "lang":
|
|
10701
11282
|
return runLang(args.positional);
|
|
11283
|
+
case "update":
|
|
11284
|
+
case "upgrade":
|
|
11285
|
+
return runUpdateCommand(args.flags);
|
|
10702
11286
|
default:
|
|
10703
11287
|
console.error(t2("cliUnknownCommand", { command: args.command }));
|
|
10704
11288
|
console.log(t2("cliUsage"));
|