codex-token-tracker 0.2.0 → 0.2.2
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 +76 -0
- package/README.md +68 -5
- package/dist/cli.js +299 -25
- package/dist/main.js +936 -43
- package/dist/preload.js +1 -0
- package/dist/renderer/renderer.js +114 -7
- package/dist/renderer/styles.css +99 -0
- package/package.json +16 -20
package/dist/cli.js
CHANGED
|
@@ -1138,9 +1138,20 @@ var init_src = __esm({
|
|
|
1138
1138
|
}
|
|
1139
1139
|
});
|
|
1140
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.2" : "0.0.0-dev";
|
|
1147
|
+
APP_CHANNEL = true ? "prod" : "dev";
|
|
1148
|
+
IS_DEV_BUILD = APP_CHANNEL === "dev";
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1141
1152
|
// src/core/config.ts
|
|
1142
1153
|
function configDir() {
|
|
1143
|
-
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");
|
|
1144
1155
|
}
|
|
1145
1156
|
function readJson(file, fallback) {
|
|
1146
1157
|
try {
|
|
@@ -1193,7 +1204,7 @@ function loadConfig() {
|
|
|
1193
1204
|
cfg.sources = normalizeSources(stored.sources);
|
|
1194
1205
|
cfg.trackAllProviders = stored.trackAllProviders === true;
|
|
1195
1206
|
cfg.liveRateLimits = stored.liveRateLimits !== false;
|
|
1196
|
-
cfg.checkUpdates = stored.checkUpdates
|
|
1207
|
+
cfg.checkUpdates = typeof stored.checkUpdates === "boolean" ? stored.checkUpdates : DEFAULT_CONFIG.checkUpdates;
|
|
1197
1208
|
if (!(cfg.usageRefreshSec >= 15)) cfg.usageRefreshSec = DEFAULT_CONFIG.usageRefreshSec;
|
|
1198
1209
|
if (!["auto", "en", "zh"].includes(cfg.language)) cfg.language = "auto";
|
|
1199
1210
|
if (!(cfg.uploadIntervalSec >= 10)) cfg.uploadIntervalSec = DEFAULT_CONFIG.uploadIntervalSec;
|
|
@@ -1300,14 +1311,17 @@ function loadPricingOverrides() {
|
|
|
1300
1311
|
}
|
|
1301
1312
|
return Object.keys(out).length ? out : void 0;
|
|
1302
1313
|
}
|
|
1303
|
-
var import_node_fs2, import_node_os2, import_node_path2, DEFAULT_DASHBOARD_URL, SOURCE_FORMATS, DEFAULT_SOURCES, SOURCE_IDS, DEFAULT_CONFIG, EDITABLE_KEYS, AGENT_NAME_RE;
|
|
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;
|
|
1304
1315
|
var init_config = __esm({
|
|
1305
1316
|
"src/core/config.ts"() {
|
|
1306
1317
|
"use strict";
|
|
1307
1318
|
import_node_fs2 = __toESM(require("node:fs"));
|
|
1308
1319
|
import_node_os2 = __toESM(require("node:os"));
|
|
1309
1320
|
import_node_path2 = __toESM(require("node:path"));
|
|
1310
|
-
|
|
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;
|
|
1311
1325
|
SOURCE_FORMATS = ["codex", "pi", "generic", "opencode", "cline"];
|
|
1312
1326
|
DEFAULT_SOURCES = { codex: true, pi: true, hermes: true, opencode: true, cline: true, roo: true, kilo: true };
|
|
1313
1327
|
SOURCE_IDS = Object.keys(DEFAULT_SOURCES);
|
|
@@ -1327,7 +1341,8 @@ var init_config = __esm({
|
|
|
1327
1341
|
trackAllProviders: false,
|
|
1328
1342
|
liveRateLimits: true,
|
|
1329
1343
|
usageRefreshSec: 60,
|
|
1330
|
-
|
|
1344
|
+
// a dev build must never offer to replace itself with the published package
|
|
1345
|
+
checkUpdates: !IS_DEV_BUILD
|
|
1331
1346
|
};
|
|
1332
1347
|
EDITABLE_KEYS = [
|
|
1333
1348
|
"dashboardUrl",
|
|
@@ -1397,6 +1412,7 @@ var init_en = __esm({
|
|
|
1397
1412
|
estimatedPricingHint: "Pricing estimated: model not in the price table",
|
|
1398
1413
|
other: "Other",
|
|
1399
1414
|
lastUpload: "Last upload {time}",
|
|
1415
|
+
lastSync: "Last full sync {time}",
|
|
1400
1416
|
neverUploaded: "Not uploaded yet",
|
|
1401
1417
|
uploadError: "Upload error: {message}",
|
|
1402
1418
|
allDevicesToday: "All devices today",
|
|
@@ -1415,10 +1431,26 @@ var init_en = __esm({
|
|
|
1415
1431
|
launchAtLogin: "Launch at login",
|
|
1416
1432
|
refresh: "Refresh",
|
|
1417
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}",
|
|
1418
1446
|
sessions: "{n} sessions",
|
|
1419
1447
|
files: "{n} files",
|
|
1420
1448
|
noData: "No usage found yet. Run Codex and this will fill in.",
|
|
1421
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}",
|
|
1422
1454
|
justNow: "just now",
|
|
1423
1455
|
never: "never",
|
|
1424
1456
|
local: "Local",
|
|
@@ -1446,6 +1478,7 @@ Commands:
|
|
|
1446
1478
|
login Connect this device to your dashboard (--dashboard <url>)
|
|
1447
1479
|
logout Disconnect this device
|
|
1448
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)
|
|
1449
1482
|
paths Show detected session directories (Codex, pi, OpenCode, Cline/Roo/Kilo, Hermes, custom)
|
|
1450
1483
|
update Install the newest published version (--check only reports)
|
|
1451
1484
|
config get|set Read or change settings (config set uploadIntervalSec 30, config set sources.pi false)
|
|
@@ -1460,9 +1493,9 @@ Options:
|
|
|
1460
1493
|
cliVersion: "codex-token-tracker {version}",
|
|
1461
1494
|
cliUnknownCommand: "Unknown command: {command}",
|
|
1462
1495
|
cliNoDisplay: "No display detected \u2014 starting agent mode (run `codex-tracker menubar` on a desktop).",
|
|
1463
|
-
cliNoElectron: "Electron
|
|
1496
|
+
cliNoElectron: "No Electron runtime available \u2014 starting agent mode.",
|
|
1464
1497
|
cliDownloadingElectron: "Electron runtime not found \u2014 downloading it now (about 100 MB, one time)\u2026",
|
|
1465
|
-
cliElectronDownloadFailed: "Electron download failed.
|
|
1498
|
+
cliElectronDownloadFailed: "Electron download failed. Check the network (set ELECTRON_MIRROR, e.g. https://npmmirror.com/mirrors/electron/, or HTTPS_PROXY) and run `codex-tracker menubar` again; the headless `codex-tracker agent` works without Electron.",
|
|
1466
1499
|
cliStartingMenubar: "Starting menu bar app\u2026",
|
|
1467
1500
|
cliLoginStart: "Connecting this device to {dashboard}",
|
|
1468
1501
|
cliLoginCode: "Your code: {code}",
|
|
@@ -1500,7 +1533,15 @@ Options:
|
|
|
1500
1533
|
cliUpdateRunning: "Running: {command}",
|
|
1501
1534
|
cliUpdateDone: "Updated to {version}. Restart the menu bar app (or rerun the CLI) to use it.",
|
|
1502
1535
|
cliUpdateFailed: "Update failed (exit {code}). Run it yourself: {command}",
|
|
1503
|
-
cliUpdateCheckFailed: "Could not reach the npm registry: {message}"
|
|
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."
|
|
1504
1545
|
};
|
|
1505
1546
|
}
|
|
1506
1547
|
});
|
|
@@ -1554,6 +1595,7 @@ var init_zh = __esm({
|
|
|
1554
1595
|
estimatedPricingHint: "\u4EF7\u683C\u4E3A\u4F30\u7B97\u503C\uFF1A\u8BE5\u6A21\u578B\u4E0D\u5728\u4EF7\u76EE\u8868\u4E2D",
|
|
1555
1596
|
other: "\u5176\u4ED6",
|
|
1556
1597
|
lastUpload: "\u4E0A\u6B21\u4E0A\u4F20 {time}",
|
|
1598
|
+
lastSync: "\u4E0A\u6B21\u5B8C\u6574\u540C\u6B65 {time}",
|
|
1557
1599
|
neverUploaded: "\u5C1A\u672A\u4E0A\u4F20",
|
|
1558
1600
|
uploadError: "\u4E0A\u4F20\u5931\u8D25\uFF1A{message}",
|
|
1559
1601
|
allDevicesToday: "\u6240\u6709\u8BBE\u5907\u4ECA\u65E5",
|
|
@@ -1572,10 +1614,26 @@ var init_zh = __esm({
|
|
|
1572
1614
|
launchAtLogin: "\u5F00\u673A\u81EA\u542F\u52A8",
|
|
1573
1615
|
refresh: "\u5237\u65B0",
|
|
1574
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}",
|
|
1575
1629
|
sessions: "{n} \u4E2A\u4F1A\u8BDD",
|
|
1576
1630
|
files: "{n} \u4E2A\u6587\u4EF6",
|
|
1577
1631
|
noData: "\u8FD8\u6CA1\u6709\u7528\u91CF\u6570\u636E\u3002\u8FD0\u884C Codex \u540E\u8FD9\u91CC\u4F1A\u81EA\u52A8\u66F4\u65B0\u3002",
|
|
1578
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}",
|
|
1579
1637
|
justNow: "\u521A\u521A",
|
|
1580
1638
|
never: "\u4ECE\u672A",
|
|
1581
1639
|
local: "\u672C\u673A",
|
|
@@ -1601,6 +1659,7 @@ var init_zh = __esm({
|
|
|
1601
1659
|
login \u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230\u4EEA\u8868\u76D8\uFF08--dashboard <url>\uFF09
|
|
1602
1660
|
logout \u65AD\u5F00\u672C\u8BBE\u5907
|
|
1603
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
|
|
1604
1663
|
paths \u663E\u793A\u68C0\u6D4B\u5230\u7684\u4F1A\u8BDD\u76EE\u5F55\uFF08Codex\u3001pi\u3001OpenCode\u3001Cline/Roo/Kilo\u3001Hermes\u3001\u81EA\u5B9A\u4E49\uFF09
|
|
1605
1664
|
update \u5B89\u88C5\u6700\u65B0\u53D1\u5E03\u7248\u672C\uFF08--check \u53EA\u68C0\u67E5\u4E0D\u5B89\u88C5\uFF09
|
|
1606
1665
|
config get|set \u8BFB\u53D6\u6216\u4FEE\u6539\u8BBE\u7F6E\uFF08config set uploadIntervalSec 30\uFF0Cconfig set sources.pi false\uFF09
|
|
@@ -1615,9 +1674,9 @@ var init_zh = __esm({
|
|
|
1615
1674
|
cliVersion: "codex-token-tracker {version}",
|
|
1616
1675
|
cliUnknownCommand: "\u672A\u77E5\u547D\u4EE4\uFF1A{command}",
|
|
1617
1676
|
cliNoDisplay: "\u672A\u68C0\u6D4B\u5230\u663E\u793A\u73AF\u5883 \u2014 \u4EE5 agent \u6A21\u5F0F\u542F\u52A8\uFF08\u684C\u9762\u73AF\u5883\u8BF7\u8FD0\u884C `codex-tracker menubar`\uFF09\u3002",
|
|
1618
|
-
cliNoElectron: "\
|
|
1677
|
+
cliNoElectron: "\u6CA1\u6709\u53EF\u7528\u7684 Electron \u8FD0\u884C\u65F6 \u2014 \u4EE5 agent \u6A21\u5F0F\u542F\u52A8\u3002",
|
|
1619
1678
|
cliDownloadingElectron: "\u672A\u627E\u5230 Electron \u8FD0\u884C\u65F6\uFF0C\u6B63\u5728\u4E0B\u8F7D\uFF08\u7EA6 100 MB\uFF0C\u4EC5\u9700\u4E00\u6B21\uFF09\u2026",
|
|
1620
|
-
cliElectronDownloadFailed: "Electron \u4E0B\u8F7D\u5931\u8D25\u3002\u53EF\
|
|
1679
|
+
cliElectronDownloadFailed: "Electron \u4E0B\u8F7D\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\uFF08\u53EF\u8BBE\u7F6E ELECTRON_MIRROR\uFF0C\u4F8B\u5982 https://npmmirror.com/mirrors/electron/\uFF0C\u6216 HTTPS_PROXY\uFF09\u540E\u518D\u6B21\u8FD0\u884C `codex-tracker menubar`\uFF1B\u65E0\u754C\u9762\u7684 `codex-tracker agent` \u65E0\u9700 Electron\u3002",
|
|
1621
1680
|
cliStartingMenubar: "\u6B63\u5728\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u2026",
|
|
1622
1681
|
cliLoginStart: "\u6B63\u5728\u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230 {dashboard}",
|
|
1623
1682
|
cliLoginCode: "\u4F60\u7684\u4EE3\u7801\uFF1A{code}",
|
|
@@ -1655,7 +1714,15 @@ var init_zh = __esm({
|
|
|
1655
1714
|
cliUpdateRunning: "\u6B63\u5728\u6267\u884C\uFF1A{command}",
|
|
1656
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",
|
|
1657
1716
|
cliUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF1A{command}",
|
|
1658
|
-
cliUpdateCheckFailed: "\u65E0\u6CD5\u8BBF\u95EE npm registry\uFF1A{message}"
|
|
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"
|
|
1659
1726
|
};
|
|
1660
1727
|
}
|
|
1661
1728
|
});
|
|
@@ -8630,11 +8697,25 @@ var init_uploader = __esm({
|
|
|
8630
8697
|
throw err;
|
|
8631
8698
|
}
|
|
8632
8699
|
}
|
|
8633
|
-
/**
|
|
8634
|
-
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();
|
|
8635
8712
|
if (this.inFlight) return { buckets: 0, sessions: 0 };
|
|
8636
8713
|
this.inFlight = true;
|
|
8637
8714
|
try {
|
|
8715
|
+
if (opts.full) {
|
|
8716
|
+
this.state.pushedBuckets = {};
|
|
8717
|
+
this.state.pushedSessions = {};
|
|
8718
|
+
}
|
|
8638
8719
|
const changed = [];
|
|
8639
8720
|
const hashes = /* @__PURE__ */ new Map();
|
|
8640
8721
|
for (const b of buckets.sort((a, b2) => a.hourStart - b2.hourStart)) {
|
|
@@ -8814,6 +8895,7 @@ var import_node_fs = __toESM(require("node:fs"));
|
|
|
8814
8895
|
var import_node_os = __toESM(require("node:os"));
|
|
8815
8896
|
var import_node_path = __toESM(require("node:path"));
|
|
8816
8897
|
var import_node_child_process = require("node:child_process");
|
|
8898
|
+
var DEFAULT_ELECTRON_VERSION = true ? "38.8.6" : "38.8.6";
|
|
8817
8899
|
function archName() {
|
|
8818
8900
|
switch (process.arch) {
|
|
8819
8901
|
case "x64":
|
|
@@ -8955,6 +9037,33 @@ async function installElectronBinary(pkgDir, log2) {
|
|
|
8955
9037
|
import_node_fs.default.writeFileSync(import_node_path.default.join(pkgDir, "path.txt"), platformExecutablePath());
|
|
8956
9038
|
return import_node_fs.default.existsSync(exe) ? exe : null;
|
|
8957
9039
|
}
|
|
9040
|
+
function managedElectronDir(configHome, version2 = DEFAULT_ELECTRON_VERSION) {
|
|
9041
|
+
return import_node_path.default.join(configHome, "electron", version2);
|
|
9042
|
+
}
|
|
9043
|
+
function ensureManagedElectronDir(configHome, version2 = DEFAULT_ELECTRON_VERSION) {
|
|
9044
|
+
const dir = managedElectronDir(configHome, version2);
|
|
9045
|
+
const manifest = import_node_path.default.join(dir, "package.json");
|
|
9046
|
+
let current = null;
|
|
9047
|
+
try {
|
|
9048
|
+
current = JSON.parse(import_node_fs.default.readFileSync(manifest, "utf8")).version ?? null;
|
|
9049
|
+
} catch {
|
|
9050
|
+
}
|
|
9051
|
+
if (current !== version2) {
|
|
9052
|
+
import_node_fs.default.mkdirSync(dir, { recursive: true });
|
|
9053
|
+
import_node_fs.default.writeFileSync(manifest, JSON.stringify({ name: "electron", version: version2, private: true }, null, 2) + "\n");
|
|
9054
|
+
}
|
|
9055
|
+
return dir;
|
|
9056
|
+
}
|
|
9057
|
+
function binaryFromPackageDir(pkgDir) {
|
|
9058
|
+
try {
|
|
9059
|
+
const rel = import_node_fs.default.readFileSync(import_node_path.default.join(pkgDir, "path.txt"), "utf8").trim();
|
|
9060
|
+
if (!rel) return null;
|
|
9061
|
+
const exe = import_node_path.default.join(pkgDir, "dist", rel);
|
|
9062
|
+
return import_node_fs.default.existsSync(exe) ? exe : null;
|
|
9063
|
+
} catch {
|
|
9064
|
+
return null;
|
|
9065
|
+
}
|
|
9066
|
+
}
|
|
8958
9067
|
|
|
8959
9068
|
// src/cli.ts
|
|
8960
9069
|
init_src();
|
|
@@ -9708,6 +9817,15 @@ var SessionStore = class {
|
|
|
9708
9817
|
onChange = null;
|
|
9709
9818
|
debounceTimer = null;
|
|
9710
9819
|
sessionCache = null;
|
|
9820
|
+
/**
|
|
9821
|
+
* Forget the parsed-file index so the next deep refresh re-reads and re-parses every transcript.
|
|
9822
|
+
* Used by the full sync: files are otherwise skipped while their size and mtime are unchanged, which
|
|
9823
|
+
* would keep stale numbers around after a parser or pricing change.
|
|
9824
|
+
*/
|
|
9825
|
+
reset() {
|
|
9826
|
+
this.files.clear();
|
|
9827
|
+
this.sessionCache = null;
|
|
9828
|
+
}
|
|
9711
9829
|
async refreshDeep() {
|
|
9712
9830
|
const o = this.getOptions();
|
|
9713
9831
|
this.roots = discoverSessionRoots({ extraSessionDirs: o.extraSessionDirs, sources: o.sources });
|
|
@@ -10107,12 +10225,8 @@ var import_node_fs9 = __toESM(require("node:fs"));
|
|
|
10107
10225
|
var import_node_path11 = __toESM(require("node:path"));
|
|
10108
10226
|
var import_node_child_process4 = require("node:child_process");
|
|
10109
10227
|
init_config();
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
var APP_VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
10113
|
-
|
|
10114
|
-
// src/core/update.ts
|
|
10115
|
-
var NPM_PACKAGE = "codex-token-tracker";
|
|
10228
|
+
init_version();
|
|
10229
|
+
var NPM_PACKAGE = true ? "codex-token-tracker" : "codex-token-tracker";
|
|
10116
10230
|
var CHECK_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
10117
10231
|
var REQUEST_TIMEOUT_MS = 8e3;
|
|
10118
10232
|
var INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
@@ -10135,7 +10249,7 @@ function compareVersions(a, b) {
|
|
|
10135
10249
|
return A.pre < B.pre ? -1 : 1;
|
|
10136
10250
|
}
|
|
10137
10251
|
function isDevBuild(version2) {
|
|
10138
|
-
return version2.startsWith("0.0.0");
|
|
10252
|
+
return IS_DEV_BUILD || version2.startsWith("0.0.0");
|
|
10139
10253
|
}
|
|
10140
10254
|
function registryBase() {
|
|
10141
10255
|
const r = process.env.CODEX_TRACKER_REGISTRY || process.env.npm_config_registry || process.env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org";
|
|
@@ -10186,7 +10300,8 @@ function updateCommand(pm, spec) {
|
|
|
10186
10300
|
return `${pm} ${updateArgs(pm, spec).join(" ")}`;
|
|
10187
10301
|
}
|
|
10188
10302
|
function cachePath() {
|
|
10189
|
-
|
|
10303
|
+
const suffix = NPM_PACKAGE === "codex-token-tracker" ? "" : `-${NPM_PACKAGE}`;
|
|
10304
|
+
return import_node_path11.default.join(configDir(), `update${suffix}.json`);
|
|
10190
10305
|
}
|
|
10191
10306
|
function readCache() {
|
|
10192
10307
|
try {
|
|
@@ -10271,6 +10386,7 @@ function runUpdate(opts = {}) {
|
|
|
10271
10386
|
}
|
|
10272
10387
|
|
|
10273
10388
|
// src/core/engine.ts
|
|
10389
|
+
init_version();
|
|
10274
10390
|
var SHALLOW_MS = 3e3;
|
|
10275
10391
|
var DEEP_MS = 6e4;
|
|
10276
10392
|
var TICK_MS = 2e3;
|
|
@@ -10278,6 +10394,7 @@ var REMOTE_MS = 6e4;
|
|
|
10278
10394
|
var LIVE_LIMITS_DEBOUNCE_MS = 1e4;
|
|
10279
10395
|
var LIVE_LIMITS_MIN_GAP_MS = 2e4;
|
|
10280
10396
|
var UPDATE_CHECK_MS = 6 * 60 * 60 * 1e3;
|
|
10397
|
+
var SYNC_BANNER_MS = 25e3;
|
|
10281
10398
|
var Engine = class extends import_node_events.EventEmitter {
|
|
10282
10399
|
constructor(opts) {
|
|
10283
10400
|
super();
|
|
@@ -10317,6 +10434,13 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10317
10434
|
update = null;
|
|
10318
10435
|
updateStatus = "idle";
|
|
10319
10436
|
updateLog = null;
|
|
10437
|
+
syncStatus = "idle";
|
|
10438
|
+
syncPhase = null;
|
|
10439
|
+
syncStartedAt = null;
|
|
10440
|
+
syncFinishedAt = null;
|
|
10441
|
+
syncError = null;
|
|
10442
|
+
syncResult = null;
|
|
10443
|
+
syncBannerTimer = null;
|
|
10320
10444
|
get heatmapWeeks() {
|
|
10321
10445
|
return this.opts.heatmapWeeks ?? 16;
|
|
10322
10446
|
}
|
|
@@ -10367,6 +10491,8 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10367
10491
|
this.timers = [];
|
|
10368
10492
|
if (this.liveLimitsTimer) clearTimeout(this.liveLimitsTimer);
|
|
10369
10493
|
this.liveLimitsTimer = null;
|
|
10494
|
+
if (this.syncBannerTimer) clearTimeout(this.syncBannerTimer);
|
|
10495
|
+
this.syncBannerTimer = null;
|
|
10370
10496
|
this.store.stopWatching();
|
|
10371
10497
|
this.pending?.abort.abort();
|
|
10372
10498
|
}
|
|
@@ -10470,6 +10596,112 @@ var Engine = class extends import_node_events.EventEmitter {
|
|
|
10470
10596
|
await this.uploader.fetchRemote(this.heatmapWeeks);
|
|
10471
10597
|
this.recompute();
|
|
10472
10598
|
}
|
|
10599
|
+
/**
|
|
10600
|
+
* Full sync ("calibrate this device"). Unlike the periodic incremental upload this deliberately
|
|
10601
|
+
* throws away every cache on the way:
|
|
10602
|
+
* 1. re-read the config so sources enabled since start-up are picked up,
|
|
10603
|
+
* 2. drop the parsed-file index and re-discover + re-parse every transcript of every agent
|
|
10604
|
+
* (Codex plus the agents running on the Codex subscription: pi, OpenCode, Cline/Roo/Kilo,
|
|
10605
|
+
* Hermes and any custom `extraSessionDirs`),
|
|
10606
|
+
* 3. recompute the aggregates with the current pricing table,
|
|
10607
|
+
* 4. re-upload *everything* — not just what changed — so the dashboard's totals for this device
|
|
10608
|
+
* are replaced by the freshly computed ones,
|
|
10609
|
+
* 5. pull the other devices' rows and the live rate limits back down.
|
|
10610
|
+
*
|
|
10611
|
+
* Returns null when a sync is already running. Never throws: failures land in the returned state.
|
|
10612
|
+
*/
|
|
10613
|
+
async syncNow() {
|
|
10614
|
+
if (this.syncStatus === "running") return null;
|
|
10615
|
+
if (this.syncBannerTimer) {
|
|
10616
|
+
clearTimeout(this.syncBannerTimer);
|
|
10617
|
+
this.syncBannerTimer = null;
|
|
10618
|
+
}
|
|
10619
|
+
const startedAt = Date.now();
|
|
10620
|
+
this.syncStatus = "running";
|
|
10621
|
+
this.syncPhase = null;
|
|
10622
|
+
this.syncStartedAt = startedAt;
|
|
10623
|
+
this.syncFinishedAt = null;
|
|
10624
|
+
this.syncError = null;
|
|
10625
|
+
const phase = (p) => {
|
|
10626
|
+
this.syncPhase = p;
|
|
10627
|
+
this.emitSnapshot();
|
|
10628
|
+
};
|
|
10629
|
+
try {
|
|
10630
|
+
phase("scanning");
|
|
10631
|
+
this.reloadConfig();
|
|
10632
|
+
this.store.reset();
|
|
10633
|
+
await this.store.refreshDeep();
|
|
10634
|
+
phase("computing");
|
|
10635
|
+
this.recompute();
|
|
10636
|
+
let uploadedBuckets = 0;
|
|
10637
|
+
let uploadedSessions = 0;
|
|
10638
|
+
const uploaded = this.opts.upload && this.signedIn;
|
|
10639
|
+
if (uploaded) {
|
|
10640
|
+
phase("uploading");
|
|
10641
|
+
const r = await this.uploader.pushAll(this.stats.buckets, this.stats.sessions, this.stats.sessionCosts, { full: true });
|
|
10642
|
+
uploadedBuckets = r.buckets;
|
|
10643
|
+
uploadedSessions = r.sessions;
|
|
10644
|
+
await this.heartbeatNow().catch(() => {
|
|
10645
|
+
});
|
|
10646
|
+
phase("downloading");
|
|
10647
|
+
await this.fetchRemoteNow();
|
|
10648
|
+
}
|
|
10649
|
+
phase("limits");
|
|
10650
|
+
await this.refreshLiveLimits(true);
|
|
10651
|
+
const finishedAt = Date.now();
|
|
10652
|
+
this.syncResult = {
|
|
10653
|
+
startedAt,
|
|
10654
|
+
finishedAt,
|
|
10655
|
+
durationMs: finishedAt - startedAt,
|
|
10656
|
+
files: this.store.fileCount,
|
|
10657
|
+
sessions: this.store.sessions().length,
|
|
10658
|
+
roots: this.store.roots.length,
|
|
10659
|
+
agents: [...new Set(this.store.roots.map((r) => r.agent))].sort(),
|
|
10660
|
+
uploadedBuckets,
|
|
10661
|
+
uploadedSessions,
|
|
10662
|
+
uploaded
|
|
10663
|
+
};
|
|
10664
|
+
this.syncStatus = "done";
|
|
10665
|
+
this.syncFinishedAt = finishedAt;
|
|
10666
|
+
this.opts.log?.(
|
|
10667
|
+
`sync: ${this.syncResult.files} files, ${this.syncResult.sessions} sessions, ${uploadedBuckets} buckets / ${uploadedSessions} sessions uploaded in ${this.syncResult.durationMs} ms`
|
|
10668
|
+
);
|
|
10669
|
+
return this.syncResult;
|
|
10670
|
+
} catch (err) {
|
|
10671
|
+
this.syncStatus = "error";
|
|
10672
|
+
this.syncFinishedAt = Date.now();
|
|
10673
|
+
this.syncError = err instanceof SignedOutError ? "not signed in" : errorMessage(err);
|
|
10674
|
+
this.opts.log?.(`sync failed: ${this.syncError}`);
|
|
10675
|
+
return null;
|
|
10676
|
+
} finally {
|
|
10677
|
+
this.syncPhase = null;
|
|
10678
|
+
this.emitSnapshot();
|
|
10679
|
+
this.scheduleSyncBannerClear();
|
|
10680
|
+
}
|
|
10681
|
+
}
|
|
10682
|
+
/** Return the sync banner to "idle" a little after it finished, keeping `last` for the footer. */
|
|
10683
|
+
scheduleSyncBannerClear() {
|
|
10684
|
+
if (this.opts.watch === false) return;
|
|
10685
|
+
if (this.syncBannerTimer) clearTimeout(this.syncBannerTimer);
|
|
10686
|
+
this.syncBannerTimer = setTimeout(() => {
|
|
10687
|
+
this.syncBannerTimer = null;
|
|
10688
|
+
if (this.syncStatus === "done" || this.syncStatus === "error") {
|
|
10689
|
+
this.syncStatus = "idle";
|
|
10690
|
+
this.emitSnapshot();
|
|
10691
|
+
}
|
|
10692
|
+
}, SYNC_BANNER_MS);
|
|
10693
|
+
this.syncBannerTimer.unref?.();
|
|
10694
|
+
}
|
|
10695
|
+
syncState() {
|
|
10696
|
+
return {
|
|
10697
|
+
status: this.syncStatus,
|
|
10698
|
+
phase: this.syncPhase,
|
|
10699
|
+
startedAt: this.syncStartedAt,
|
|
10700
|
+
finishedAt: this.syncFinishedAt,
|
|
10701
|
+
error: this.syncError,
|
|
10702
|
+
last: this.syncResult
|
|
10703
|
+
};
|
|
10704
|
+
}
|
|
10473
10705
|
/**
|
|
10474
10706
|
* Ask the npm registry for the newest published version (cached for 6 h unless `force`).
|
|
10475
10707
|
* Best-effort: a failure is recorded on the snapshot, never thrown.
|
|
@@ -10587,6 +10819,7 @@ ${r.output}`.trim();
|
|
|
10587
10819
|
const sessions = this.store.sessions();
|
|
10588
10820
|
return {
|
|
10589
10821
|
version: APP_VERSION,
|
|
10822
|
+
channel: APP_CHANNEL,
|
|
10590
10823
|
generatedAt: Date.now(),
|
|
10591
10824
|
language: this.language(),
|
|
10592
10825
|
languageSetting: this.config.language,
|
|
@@ -10605,6 +10838,7 @@ ${r.output}`.trim();
|
|
|
10605
10838
|
rateLimitsError: this.config.liveRateLimits ? this.liveLimitsError : null,
|
|
10606
10839
|
rateLimitsUpdatedAt: this.liveLimits ? this.liveLimitsAt : s.logRateLimits?.observedAt ?? null,
|
|
10607
10840
|
update: this.updateState(),
|
|
10841
|
+
sync: this.syncState(),
|
|
10608
10842
|
modelsToday: s.modelsToday,
|
|
10609
10843
|
modelsMonth: s.modelsMonth,
|
|
10610
10844
|
byAgentToday: s.byAgentToday,
|
|
@@ -10621,6 +10855,7 @@ ${r.output}`.trim();
|
|
|
10621
10855
|
},
|
|
10622
10856
|
sessionDirs: SessionStore.rootDirs(this.store.roots),
|
|
10623
10857
|
sessionRoots: this.store.roots.map((r) => ({ dir: r.dir, agent: r.agent, format: r.format, origin: r.origin })),
|
|
10858
|
+
configDir: configDir(),
|
|
10624
10859
|
launchAtLogin: this.config.launchAtLogin,
|
|
10625
10860
|
trayTitle: this.config.trayTitle
|
|
10626
10861
|
};
|
|
@@ -10632,6 +10867,7 @@ init_platform();
|
|
|
10632
10867
|
init_config();
|
|
10633
10868
|
init_uploader();
|
|
10634
10869
|
init_i18n();
|
|
10870
|
+
init_version();
|
|
10635
10871
|
function parseArgs2(argv) {
|
|
10636
10872
|
const out = { command: null, positional: [], flags: {} };
|
|
10637
10873
|
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
@@ -10663,10 +10899,10 @@ function applyDashboardFlag(flags) {
|
|
|
10663
10899
|
function electronBinary() {
|
|
10664
10900
|
try {
|
|
10665
10901
|
const p = require("electron");
|
|
10666
|
-
|
|
10902
|
+
if (typeof p === "string" && p && import_node_fs10.default.existsSync(p)) return p;
|
|
10667
10903
|
} catch {
|
|
10668
|
-
return null;
|
|
10669
10904
|
}
|
|
10905
|
+
return binaryFromPackageDir(managedElectronDir(configDir()));
|
|
10670
10906
|
}
|
|
10671
10907
|
function electronPackageDir() {
|
|
10672
10908
|
try {
|
|
@@ -10678,8 +10914,7 @@ function electronPackageDir() {
|
|
|
10678
10914
|
async function ensureElectron() {
|
|
10679
10915
|
const existing = electronBinary();
|
|
10680
10916
|
if (existing) return existing;
|
|
10681
|
-
const dir = electronPackageDir();
|
|
10682
|
-
if (!dir) return null;
|
|
10917
|
+
const dir = electronPackageDir() ?? ensureManagedElectronDir(configDir());
|
|
10683
10918
|
const t2 = makeT(lang());
|
|
10684
10919
|
console.log(t2("cliDownloadingElectron"));
|
|
10685
10920
|
const log2 = { info: (m) => console.log(m), error: (m) => console.error(m) };
|
|
@@ -10797,6 +11032,7 @@ async function runStatus(json) {
|
|
|
10797
11032
|
s.auth.status === "signedIn" ? `${t2("signedInAs", { name: s.auth.user?.name || s.auth.user?.email || "?" })} \xB7 ${s.auth.dashboardUrl}` : t2("cliNotSignedIn")
|
|
10798
11033
|
);
|
|
10799
11034
|
line(t2("cliStatusDirs"), s.sessionDirs.join("\n" + " ".repeat(15)) || "-");
|
|
11035
|
+
if (IS_DEV_BUILD) console.log(t2("cliChannelDev", { url: s.auth.dashboardUrl, dir: configDir() }));
|
|
10800
11036
|
const byAgent = Object.entries(s.counts.byAgent).sort((a, b) => b[1].sessions - a[1].sessions).map(([agent, c]) => `${agent} ${c.sessions}`).join(", ");
|
|
10801
11037
|
console.log(t2("sessions", { n: s.counts.sessions }) + " \xB7 " + t2("files", { n: s.counts.files }) + (byAgent ? ` (${byAgent})` : ""));
|
|
10802
11038
|
if (cfg.checkUpdates) {
|
|
@@ -10805,6 +11041,36 @@ async function runStatus(json) {
|
|
|
10805
11041
|
}
|
|
10806
11042
|
return 0;
|
|
10807
11043
|
}
|
|
11044
|
+
async function runSync(flags) {
|
|
11045
|
+
const cfg = applyDashboardFlag(flags);
|
|
11046
|
+
const L = lang(cfg);
|
|
11047
|
+
const t2 = makeT(L);
|
|
11048
|
+
const engine = new Engine({
|
|
11049
|
+
upload: true,
|
|
11050
|
+
watch: false,
|
|
11051
|
+
systemLocale: systemLocale(),
|
|
11052
|
+
log: process.env.CODEX_TRACKER_DEBUG ? (m) => console.error("[sync]", m) : void 0
|
|
11053
|
+
});
|
|
11054
|
+
console.log(t2("cliSyncStart"));
|
|
11055
|
+
const result = await engine.syncNow();
|
|
11056
|
+
if (!result) {
|
|
11057
|
+
console.error(t2("cliSyncFailed", { message: engine.snapshot().sync.error ?? "?" }));
|
|
11058
|
+
return 1;
|
|
11059
|
+
}
|
|
11060
|
+
console.log(t2("cliSyncScanned", { files: result.files, roots: result.roots, agents: result.agents.join(", ") || "-" }));
|
|
11061
|
+
if (result.uploaded) console.log(t2("cliSyncUploaded", { buckets: result.uploadedBuckets, sessions: result.uploadedSessions }));
|
|
11062
|
+
else console.log(t2("cliSyncLocal"));
|
|
11063
|
+
const s = engine.snapshot();
|
|
11064
|
+
console.log(
|
|
11065
|
+
t2("cliSyncDone", {
|
|
11066
|
+
seconds: (result.durationMs / 1e3).toFixed(1),
|
|
11067
|
+
sessions: result.sessions,
|
|
11068
|
+
tokens: formatTokens(s.today.usage.total),
|
|
11069
|
+
cost: formatUSD(s.today.cost)
|
|
11070
|
+
})
|
|
11071
|
+
);
|
|
11072
|
+
return 0;
|
|
11073
|
+
}
|
|
10808
11074
|
async function runLogin(flags) {
|
|
10809
11075
|
const cfg = applyDashboardFlag(flags);
|
|
10810
11076
|
const L = lang(cfg);
|
|
@@ -10908,6 +11174,7 @@ function runPaths() {
|
|
|
10908
11174
|
const off = SOURCE_IDS.filter((id) => !cfg.sources[id]);
|
|
10909
11175
|
if (off.length) console.log(` (disabled: ${off.join(", ")})`);
|
|
10910
11176
|
console.log(t2("cliConfigDir", { dir: configDir() }));
|
|
11177
|
+
if (IS_DEV_BUILD) console.log(t2("cliChannelDev", { url: cfg.dashboardUrl, dir: configDir() }));
|
|
10911
11178
|
return 0;
|
|
10912
11179
|
}
|
|
10913
11180
|
function runConfig(positional) {
|
|
@@ -10954,6 +11221,10 @@ function runConfig(positional) {
|
|
|
10954
11221
|
}
|
|
10955
11222
|
async function runUpdateCommand(flags) {
|
|
10956
11223
|
const t2 = makeT(lang());
|
|
11224
|
+
if (IS_DEV_BUILD) {
|
|
11225
|
+
console.error(t2("cliUpdateDevBuild"));
|
|
11226
|
+
return 1;
|
|
11227
|
+
}
|
|
10957
11228
|
const info2 = await checkForUpdate({ force: true });
|
|
10958
11229
|
if (info2.error && !info2.latest) {
|
|
10959
11230
|
console.error(t2("cliUpdateCheckFailed", { message: info2.error }));
|
|
@@ -10992,6 +11263,7 @@ async function main() {
|
|
|
10992
11263
|
const t2 = makeT(lang());
|
|
10993
11264
|
if (args.flags.version) {
|
|
10994
11265
|
console.log(t2("cliVersion", { version: APP_VERSION }));
|
|
11266
|
+
if (IS_DEV_BUILD) console.log(t2("cliChannelDev", { url: loadConfig().dashboardUrl, dir: configDir() }));
|
|
10995
11267
|
return 0;
|
|
10996
11268
|
}
|
|
10997
11269
|
if (args.flags.help || args.command === "help") {
|
|
@@ -11028,6 +11300,8 @@ async function main() {
|
|
|
11028
11300
|
}
|
|
11029
11301
|
case "status":
|
|
11030
11302
|
return runStatus(args.flags.json === true);
|
|
11303
|
+
case "sync":
|
|
11304
|
+
return runSync(args.flags);
|
|
11031
11305
|
case "paths":
|
|
11032
11306
|
return runPaths();
|
|
11033
11307
|
case "config":
|