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/dist/main.js CHANGED
@@ -5,6 +5,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require2() {
9
+ try {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ } catch (e) {
12
+ throw mod = 0, e;
13
+ }
14
+ };
8
15
  var __copyProps = (to, from, except, desc) => {
9
16
  if (from && typeof from === "object" || typeof from === "function") {
10
17
  for (let key of __getOwnPropNames(from))
@@ -22,13 +29,691 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
29
  mod
23
30
  ));
24
31
 
32
+ // ../../node_modules/.pnpm/electron-positioner@4.1.0/node_modules/electron-positioner/index.js
33
+ var require_electron_positioner = __commonJS({
34
+ "../../node_modules/.pnpm/electron-positioner@4.1.0/node_modules/electron-positioner/index.js"(exports2, module2) {
35
+ "use strict";
36
+ module2.exports = class Positioner {
37
+ constructor(browserWindow) {
38
+ this.browserWindow = browserWindow;
39
+ this.electronScreen = require("electron").screen;
40
+ }
41
+ _getCoords(position, trayPosition) {
42
+ let screenSize = this._getScreenSize(trayPosition);
43
+ let windowSize = this._getWindowSize();
44
+ if (trayPosition === void 0) trayPosition = {};
45
+ let positions = {
46
+ trayLeft: {
47
+ x: Math.floor(trayPosition.x),
48
+ y: screenSize.y
49
+ },
50
+ trayBottomLeft: {
51
+ x: Math.floor(trayPosition.x),
52
+ y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y))
53
+ },
54
+ trayRight: {
55
+ x: Math.floor(trayPosition.x - windowSize[0] + trayPosition.width),
56
+ y: screenSize.y
57
+ },
58
+ trayBottomRight: {
59
+ x: Math.floor(trayPosition.x - windowSize[0] + trayPosition.width),
60
+ y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y))
61
+ },
62
+ trayCenter: {
63
+ x: Math.floor(trayPosition.x - windowSize[0] / 2 + trayPosition.width / 2),
64
+ y: screenSize.y
65
+ },
66
+ trayBottomCenter: {
67
+ x: Math.floor(trayPosition.x - windowSize[0] / 2 + trayPosition.width / 2),
68
+ y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y))
69
+ },
70
+ topLeft: {
71
+ x: screenSize.x,
72
+ y: screenSize.y
73
+ },
74
+ topRight: {
75
+ x: Math.floor(screenSize.x + (screenSize.width - windowSize[0])),
76
+ y: screenSize.y
77
+ },
78
+ bottomLeft: {
79
+ x: screenSize.x,
80
+ y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y))
81
+ },
82
+ bottomRight: {
83
+ x: Math.floor(screenSize.x + (screenSize.width - windowSize[0])),
84
+ y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y))
85
+ },
86
+ topCenter: {
87
+ x: Math.floor(screenSize.x + (screenSize.width / 2 - windowSize[0] / 2)),
88
+ y: screenSize.y
89
+ },
90
+ bottomCenter: {
91
+ x: Math.floor(screenSize.x + (screenSize.width / 2 - windowSize[0] / 2)),
92
+ y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y))
93
+ },
94
+ leftCenter: {
95
+ x: screenSize.x,
96
+ y: screenSize.y + Math.floor(screenSize.height / 2) - Math.floor(windowSize[1] / 2)
97
+ },
98
+ rightCenter: {
99
+ x: Math.floor(screenSize.x + (screenSize.width - windowSize[0])),
100
+ y: screenSize.y + Math.floor(screenSize.height / 2) - Math.floor(windowSize[1] / 2)
101
+ },
102
+ center: {
103
+ x: Math.floor(screenSize.x + (screenSize.width / 2 - windowSize[0] / 2)),
104
+ y: Math.floor((screenSize.height + screenSize.y) / 2 - windowSize[1] / 2)
105
+ }
106
+ };
107
+ if (position.substr(0, 4) === "tray") {
108
+ if (positions[position].x + windowSize[0] > screenSize.width + screenSize.x) {
109
+ return {
110
+ x: positions["topRight"].x,
111
+ y: positions[position].y
112
+ };
113
+ }
114
+ }
115
+ return positions[position];
116
+ }
117
+ _getWindowSize() {
118
+ return this.browserWindow.getSize();
119
+ }
120
+ _getScreenSize(trayPosition) {
121
+ if (trayPosition) {
122
+ return this.electronScreen.getDisplayMatching(trayPosition).workArea;
123
+ } else {
124
+ return this.electronScreen.getDisplayNearestPoint(this.electronScreen.getCursorScreenPoint()).workArea;
125
+ }
126
+ }
127
+ move(position, trayPos) {
128
+ var coords = this._getCoords(position, trayPos);
129
+ this.browserWindow.setPosition(coords.x, coords.y);
130
+ }
131
+ calculate(position, trayPos) {
132
+ var coords = this._getCoords(position, trayPos);
133
+ return {
134
+ x: coords.x,
135
+ y: coords.y
136
+ };
137
+ }
138
+ };
139
+ }
140
+ });
141
+
142
+ // ../../node_modules/.pnpm/menubar@9.5.3_electron@38.8.6/node_modules/menubar/lib/util/cleanOptions.js
143
+ var require_cleanOptions = __commonJS({
144
+ "../../node_modules/.pnpm/menubar@9.5.3_electron@38.8.6/node_modules/menubar/lib/util/cleanOptions.js"(exports2) {
145
+ "use strict";
146
+ var __assign = exports2 && exports2.__assign || function() {
147
+ __assign = Object.assign || function(t2) {
148
+ for (var s, i2 = 1, n = arguments.length; i2 < n; i2++) {
149
+ s = arguments[i2];
150
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
151
+ t2[p] = s[p];
152
+ }
153
+ return t2;
154
+ };
155
+ return __assign.apply(this, arguments);
156
+ };
157
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
158
+ return mod && mod.__esModule ? mod : { "default": mod };
159
+ };
160
+ Object.defineProperty(exports2, "__esModule", { value: true });
161
+ exports2.cleanOptions = void 0;
162
+ var path_1 = __importDefault(require("path"));
163
+ var url_1 = __importDefault(require("url"));
164
+ var electron_1 = require("electron");
165
+ var DEFAULT_WINDOW_HEIGHT = 400;
166
+ var DEFAULT_WINDOW_WIDTH = 400;
167
+ function cleanOptions(opts) {
168
+ var options = __assign({}, opts);
169
+ if (options.activateWithApp === void 0) {
170
+ options.activateWithApp = true;
171
+ }
172
+ if (!options.dir) {
173
+ options.dir = electron_1.app.getAppPath();
174
+ }
175
+ if (!path_1.default.isAbsolute(options.dir)) {
176
+ options.dir = path_1.default.resolve(options.dir);
177
+ }
178
+ if (options.index === void 0) {
179
+ options.index = url_1.default.format({
180
+ pathname: path_1.default.join(options.dir, "index.html"),
181
+ protocol: "file:",
182
+ slashes: true
183
+ });
184
+ }
185
+ options.loadUrlOptions = options.loadUrlOptions || {};
186
+ options.tooltip = options.tooltip || "";
187
+ if (!options.browserWindow) {
188
+ options.browserWindow = {};
189
+ }
190
+ options.browserWindow.width = // Note: not using `options.browserWindow.width || DEFAULT_WINDOW_WIDTH` so
191
+ // that users can put a 0 width
192
+ options.browserWindow.width !== void 0 ? options.browserWindow.width : DEFAULT_WINDOW_WIDTH;
193
+ options.browserWindow.height = options.browserWindow.height !== void 0 ? options.browserWindow.height : DEFAULT_WINDOW_HEIGHT;
194
+ return options;
195
+ }
196
+ exports2.cleanOptions = cleanOptions;
197
+ }
198
+ });
199
+
200
+ // ../../node_modules/.pnpm/menubar@9.5.3_electron@38.8.6/node_modules/menubar/lib/util/getWindowPosition.js
201
+ var require_getWindowPosition = __commonJS({
202
+ "../../node_modules/.pnpm/menubar@9.5.3_electron@38.8.6/node_modules/menubar/lib/util/getWindowPosition.js"(exports2) {
203
+ "use strict";
204
+ Object.defineProperty(exports2, "__esModule", { value: true });
205
+ exports2.getWindowPosition = exports2.taskbarLocation = void 0;
206
+ var electron_1 = require("electron");
207
+ var isLinux = process.platform === "linux";
208
+ var trayToScreenRects = function(tray) {
209
+ var _a2 = electron_1.screen.getDisplayMatching(tray.getBounds()), workArea = _a2.workArea, screenBounds = _a2.bounds;
210
+ workArea.x -= screenBounds.x;
211
+ workArea.y -= screenBounds.y;
212
+ return [screenBounds, workArea];
213
+ };
214
+ function taskbarLocation(tray) {
215
+ var _a2 = trayToScreenRects(tray), screenBounds = _a2[0], workArea = _a2[1];
216
+ if (workArea.x > 0) {
217
+ if (isLinux && workArea.y > 0)
218
+ return "top";
219
+ return "left";
220
+ }
221
+ if (workArea.y > 0) {
222
+ return "top";
223
+ }
224
+ if (workArea.width < screenBounds.width) {
225
+ return "right";
226
+ }
227
+ return "bottom";
228
+ }
229
+ exports2.taskbarLocation = taskbarLocation;
230
+ function getWindowPosition(tray) {
231
+ switch (process.platform) {
232
+ // macOS
233
+ // Supports top taskbars
234
+ case "darwin":
235
+ return "trayCenter";
236
+ // Linux
237
+ // Windows
238
+ // Supports top/bottom/left/right taskbar
239
+ case "linux":
240
+ case "win32": {
241
+ var traySide = taskbarLocation(tray);
242
+ if (traySide === "top") {
243
+ return isLinux ? "topRight" : "trayCenter";
244
+ }
245
+ if (traySide === "bottom") {
246
+ return "bottomRight";
247
+ }
248
+ if (traySide === "left") {
249
+ return "bottomLeft";
250
+ }
251
+ if (traySide === "right") {
252
+ return "bottomRight";
253
+ }
254
+ }
255
+ }
256
+ return "topRight";
257
+ }
258
+ exports2.getWindowPosition = getWindowPosition;
259
+ }
260
+ });
261
+
262
+ // ../../node_modules/.pnpm/menubar@9.5.3_electron@38.8.6/node_modules/menubar/lib/Menubar.js
263
+ var require_Menubar = __commonJS({
264
+ "../../node_modules/.pnpm/menubar@9.5.3_electron@38.8.6/node_modules/menubar/lib/Menubar.js"(exports2) {
265
+ "use strict";
266
+ var __extends = exports2 && exports2.__extends || /* @__PURE__ */ (function() {
267
+ var extendStatics = function(d, b) {
268
+ extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) {
269
+ d2.__proto__ = b2;
270
+ } || function(d2, b2) {
271
+ for (var p in b2) if (Object.prototype.hasOwnProperty.call(b2, p)) d2[p] = b2[p];
272
+ };
273
+ return extendStatics(d, b);
274
+ };
275
+ return function(d, b) {
276
+ if (typeof b !== "function" && b !== null)
277
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
278
+ extendStatics(d, b);
279
+ function __() {
280
+ this.constructor = d;
281
+ }
282
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
283
+ };
284
+ })();
285
+ var __assign = exports2 && exports2.__assign || function() {
286
+ __assign = Object.assign || function(t2) {
287
+ for (var s, i2 = 1, n = arguments.length; i2 < n; i2++) {
288
+ s = arguments[i2];
289
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
290
+ t2[p] = s[p];
291
+ }
292
+ return t2;
293
+ };
294
+ return __assign.apply(this, arguments);
295
+ };
296
+ var __awaiter = exports2 && exports2.__awaiter || function(thisArg, _arguments, P, generator) {
297
+ function adopt(value) {
298
+ return value instanceof P ? value : new P(function(resolve) {
299
+ resolve(value);
300
+ });
301
+ }
302
+ return new (P || (P = Promise))(function(resolve, reject) {
303
+ function fulfilled(value) {
304
+ try {
305
+ step(generator.next(value));
306
+ } catch (e) {
307
+ reject(e);
308
+ }
309
+ }
310
+ function rejected(value) {
311
+ try {
312
+ step(generator["throw"](value));
313
+ } catch (e) {
314
+ reject(e);
315
+ }
316
+ }
317
+ function step(result) {
318
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
319
+ }
320
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
321
+ });
322
+ };
323
+ var __generator = exports2 && exports2.__generator || function(thisArg, body) {
324
+ var _ = { label: 0, sent: function() {
325
+ if (t2[0] & 1) throw t2[1];
326
+ return t2[1];
327
+ }, trys: [], ops: [] }, f, y, t2, g;
328
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() {
329
+ return this;
330
+ }), g;
331
+ function verb(n) {
332
+ return function(v2) {
333
+ return step([n, v2]);
334
+ };
335
+ }
336
+ function step(op) {
337
+ if (f) throw new TypeError("Generator is already executing.");
338
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
339
+ if (f = 1, y && (t2 = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t2 = y["return"]) && t2.call(y), 0) : y.next) && !(t2 = t2.call(y, op[1])).done) return t2;
340
+ if (y = 0, t2) op = [op[0] & 2, t2.value];
341
+ switch (op[0]) {
342
+ case 0:
343
+ case 1:
344
+ t2 = op;
345
+ break;
346
+ case 4:
347
+ _.label++;
348
+ return { value: op[1], done: false };
349
+ case 5:
350
+ _.label++;
351
+ y = op[1];
352
+ op = [0];
353
+ continue;
354
+ case 7:
355
+ op = _.ops.pop();
356
+ _.trys.pop();
357
+ continue;
358
+ default:
359
+ if (!(t2 = _.trys, t2 = t2.length > 0 && t2[t2.length - 1]) && (op[0] === 6 || op[0] === 2)) {
360
+ _ = 0;
361
+ continue;
362
+ }
363
+ if (op[0] === 3 && (!t2 || op[1] > t2[0] && op[1] < t2[3])) {
364
+ _.label = op[1];
365
+ break;
366
+ }
367
+ if (op[0] === 6 && _.label < t2[1]) {
368
+ _.label = t2[1];
369
+ t2 = op;
370
+ break;
371
+ }
372
+ if (t2 && _.label < t2[2]) {
373
+ _.label = t2[2];
374
+ _.ops.push(op);
375
+ break;
376
+ }
377
+ if (t2[2]) _.ops.pop();
378
+ _.trys.pop();
379
+ continue;
380
+ }
381
+ op = body.call(thisArg, _);
382
+ } catch (e) {
383
+ op = [6, e];
384
+ y = 0;
385
+ } finally {
386
+ f = t2 = 0;
387
+ }
388
+ if (op[0] & 5) throw op[1];
389
+ return { value: op[0] ? op[1] : void 0, done: true };
390
+ }
391
+ };
392
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
393
+ return mod && mod.__esModule ? mod : { "default": mod };
394
+ };
395
+ Object.defineProperty(exports2, "__esModule", { value: true });
396
+ exports2.Menubar = void 0;
397
+ var events_1 = require("events");
398
+ var fs_1 = __importDefault(require("fs"));
399
+ var path_1 = __importDefault(require("path"));
400
+ var electron_1 = require("electron");
401
+ var electron_positioner_1 = __importDefault(require_electron_positioner());
402
+ var cleanOptions_1 = require_cleanOptions();
403
+ var getWindowPosition_1 = require_getWindowPosition();
404
+ var Menubar = (
405
+ /** @class */
406
+ (function(_super) {
407
+ __extends(Menubar2, _super);
408
+ function Menubar2(app2, options) {
409
+ var _this = _super.call(this) || this;
410
+ _this._blurTimeout = null;
411
+ _this._app = app2;
412
+ _this._options = (0, cleanOptions_1.cleanOptions)(options);
413
+ _this._isVisible = false;
414
+ if (app2.isReady()) {
415
+ process.nextTick(function() {
416
+ return _this.appReady().catch(function(err) {
417
+ return console.error("menubar: ", err);
418
+ });
419
+ });
420
+ } else {
421
+ app2.on("ready", function() {
422
+ _this.appReady().catch(function(err) {
423
+ return console.error("menubar: ", err);
424
+ });
425
+ });
426
+ }
427
+ return _this;
428
+ }
429
+ Object.defineProperty(Menubar2.prototype, "app", {
430
+ /**
431
+ * The Electron [App](https://electronjs.org/docs/api/app)
432
+ * instance.
433
+ */
434
+ get: function() {
435
+ return this._app;
436
+ },
437
+ enumerable: false,
438
+ configurable: true
439
+ });
440
+ Object.defineProperty(Menubar2.prototype, "positioner", {
441
+ /**
442
+ * The [electron-positioner](https://github.com/jenslind/electron-positioner)
443
+ * instance.
444
+ */
445
+ get: function() {
446
+ if (!this._positioner) {
447
+ throw new Error("Please access `this.positioner` after the `after-create-window` event has fired.");
448
+ }
449
+ return this._positioner;
450
+ },
451
+ enumerable: false,
452
+ configurable: true
453
+ });
454
+ Object.defineProperty(Menubar2.prototype, "tray", {
455
+ /**
456
+ * The Electron [Tray](https://electronjs.org/docs/api/tray) instance.
457
+ */
458
+ get: function() {
459
+ if (!this._tray) {
460
+ throw new Error("Please access `this.tray` after the `ready` event has fired.");
461
+ }
462
+ return this._tray;
463
+ },
464
+ enumerable: false,
465
+ configurable: true
466
+ });
467
+ Object.defineProperty(Menubar2.prototype, "window", {
468
+ /**
469
+ * The Electron [BrowserWindow](https://electronjs.org/docs/api/browser-window)
470
+ * instance, if it's present.
471
+ */
472
+ get: function() {
473
+ return this._browserWindow;
474
+ },
475
+ enumerable: false,
476
+ configurable: true
477
+ });
478
+ Menubar2.prototype.getOption = function(key) {
479
+ return this._options[key];
480
+ };
481
+ Menubar2.prototype.hideWindow = function() {
482
+ if (!this._browserWindow || !this._isVisible) {
483
+ return;
484
+ }
485
+ this.emit("hide");
486
+ this._browserWindow.hide();
487
+ this.emit("after-hide");
488
+ this._isVisible = false;
489
+ if (this._blurTimeout) {
490
+ clearTimeout(this._blurTimeout);
491
+ this._blurTimeout = null;
492
+ }
493
+ };
494
+ Menubar2.prototype.setOption = function(key, value) {
495
+ this._options[key] = value;
496
+ };
497
+ Menubar2.prototype.showWindow = function(trayPos) {
498
+ return __awaiter(this, void 0, void 0, function() {
499
+ var noBoundsPosition, position, x, y;
500
+ return __generator(this, function(_a2) {
501
+ switch (_a2.label) {
502
+ case 0:
503
+ if (!this.tray) {
504
+ throw new Error("Tray should have been instantiated by now");
505
+ }
506
+ if (!!this._browserWindow) return [3, 2];
507
+ return [4, this.createWindow()];
508
+ case 1:
509
+ _a2.sent();
510
+ _a2.label = 2;
511
+ case 2:
512
+ if (!this._browserWindow) {
513
+ throw new Error("Window has been initialized just above. qed.");
514
+ }
515
+ if (["win32", "linux"].includes(process.platform)) {
516
+ this._options.windowPosition = (0, getWindowPosition_1.getWindowPosition)(this.tray);
517
+ }
518
+ this.emit("show");
519
+ if (trayPos && trayPos.x !== 0) {
520
+ this._cachedBounds = trayPos;
521
+ } else if (this._cachedBounds) {
522
+ trayPos = this._cachedBounds;
523
+ } else if (this.tray.getBounds) {
524
+ trayPos = this.tray.getBounds();
525
+ }
526
+ noBoundsPosition = void 0;
527
+ if ((trayPos === void 0 || trayPos.x === 0) && this._options.windowPosition && this._options.windowPosition.startsWith("tray")) {
528
+ noBoundsPosition = process.platform === "win32" ? "bottomRight" : "topRight";
529
+ }
530
+ position = this.positioner.calculate(this._options.windowPosition || noBoundsPosition, trayPos);
531
+ x = this._options.browserWindow.x !== void 0 ? this._options.browserWindow.x : position.x;
532
+ y = this._options.browserWindow.y !== void 0 ? this._options.browserWindow.y : position.y;
533
+ this._browserWindow.setPosition(Math.round(x), Math.round(y));
534
+ this._browserWindow.show();
535
+ this._isVisible = true;
536
+ this.emit("after-show");
537
+ return [
538
+ 2
539
+ /*return*/
540
+ ];
541
+ }
542
+ });
543
+ });
544
+ };
545
+ Menubar2.prototype.appReady = function() {
546
+ return __awaiter(this, void 0, void 0, function() {
547
+ var trayImage, defaultClickEvent;
548
+ var _this = this;
549
+ return __generator(this, function(_a2) {
550
+ switch (_a2.label) {
551
+ case 0:
552
+ if (this.app.dock && !this._options.showDockIcon) {
553
+ this.app.dock.hide();
554
+ }
555
+ if (this._options.activateWithApp) {
556
+ this.app.on("activate", function(_event, hasVisibleWindows) {
557
+ if (!hasVisibleWindows) {
558
+ _this.showWindow().catch(console.error);
559
+ }
560
+ });
561
+ }
562
+ trayImage = this._options.icon || path_1.default.join(this._options.dir, "IconTemplate.png");
563
+ if (typeof trayImage === "string" && !fs_1.default.existsSync(trayImage)) {
564
+ trayImage = path_1.default.join(__dirname, "..", "assets", "IconTemplate.png");
565
+ }
566
+ defaultClickEvent = this._options.showOnRightClick ? "right-click" : "click";
567
+ this._tray = this._options.tray || new electron_1.Tray(trayImage);
568
+ if (!this.tray) {
569
+ throw new Error("Tray has been initialized above");
570
+ }
571
+ this.tray.on(defaultClickEvent, this.clicked.bind(this));
572
+ this.tray.on("double-click", this.clicked.bind(this));
573
+ this.tray.setToolTip(this._options.tooltip);
574
+ if (!this._options.windowPosition) {
575
+ this._options.windowPosition = (0, getWindowPosition_1.getWindowPosition)(this.tray);
576
+ }
577
+ if (!this._options.preloadWindow) return [3, 2];
578
+ return [4, this.createWindow()];
579
+ case 1:
580
+ _a2.sent();
581
+ _a2.label = 2;
582
+ case 2:
583
+ this.emit("ready");
584
+ return [
585
+ 2
586
+ /*return*/
587
+ ];
588
+ }
589
+ });
590
+ });
591
+ };
592
+ Menubar2.prototype.clicked = function(event, bounds) {
593
+ return __awaiter(this, void 0, void 0, function() {
594
+ return __generator(this, function(_a2) {
595
+ switch (_a2.label) {
596
+ case 0:
597
+ if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) {
598
+ return [2, this.hideWindow()];
599
+ }
600
+ if (this._blurTimeout) {
601
+ clearInterval(this._blurTimeout);
602
+ }
603
+ if (this._browserWindow && this._isVisible) {
604
+ return [2, this.hideWindow()];
605
+ }
606
+ this._cachedBounds = bounds || this._cachedBounds;
607
+ return [4, this.showWindow(this._cachedBounds)];
608
+ case 1:
609
+ _a2.sent();
610
+ return [
611
+ 2
612
+ /*return*/
613
+ ];
614
+ }
615
+ });
616
+ });
617
+ };
618
+ Menubar2.prototype.createWindow = function() {
619
+ return __awaiter(this, void 0, void 0, function() {
620
+ var defaults;
621
+ var _this = this;
622
+ return __generator(this, function(_a2) {
623
+ switch (_a2.label) {
624
+ case 0:
625
+ this.emit("create-window");
626
+ defaults = {
627
+ show: false,
628
+ frame: false
629
+ // Remove window frame
630
+ };
631
+ this._browserWindow = new electron_1.BrowserWindow(__assign(__assign({}, defaults), this._options.browserWindow));
632
+ this._positioner = new electron_positioner_1.default(this._browserWindow);
633
+ this._browserWindow.on("blur", function() {
634
+ if (!_this._browserWindow) {
635
+ return;
636
+ }
637
+ _this._browserWindow.isAlwaysOnTop() ? _this.emit("focus-lost") : _this._blurTimeout = setTimeout(function() {
638
+ _this.hideWindow();
639
+ }, 100);
640
+ });
641
+ if (this._options.showOnAllWorkspaces !== false) {
642
+ this._browserWindow.setVisibleOnAllWorkspaces(true, {
643
+ skipTransformProcessType: true
644
+ // Avoid damaging the original visible state of app.dock
645
+ });
646
+ }
647
+ this._browserWindow.on("close", this.windowClear.bind(this));
648
+ this.emit("before-load");
649
+ if (!(this._options.index !== false)) return [3, 2];
650
+ return [4, this._browserWindow.loadURL(this._options.index, this._options.loadUrlOptions)];
651
+ case 1:
652
+ _a2.sent();
653
+ _a2.label = 2;
654
+ case 2:
655
+ this.emit("after-create-window");
656
+ return [
657
+ 2
658
+ /*return*/
659
+ ];
660
+ }
661
+ });
662
+ });
663
+ };
664
+ Menubar2.prototype.windowClear = function() {
665
+ this._browserWindow = void 0;
666
+ this.emit("after-close");
667
+ };
668
+ return Menubar2;
669
+ })(events_1.EventEmitter)
670
+ );
671
+ exports2.Menubar = Menubar;
672
+ }
673
+ });
674
+
675
+ // ../../node_modules/.pnpm/menubar@9.5.3_electron@38.8.6/node_modules/menubar/lib/index.js
676
+ var require_lib = __commonJS({
677
+ "../../node_modules/.pnpm/menubar@9.5.3_electron@38.8.6/node_modules/menubar/lib/index.js"(exports2) {
678
+ "use strict";
679
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
680
+ if (k2 === void 0) k2 = k;
681
+ var desc = Object.getOwnPropertyDescriptor(m, k);
682
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
683
+ desc = { enumerable: true, get: function() {
684
+ return m[k];
685
+ } };
686
+ }
687
+ Object.defineProperty(o, k2, desc);
688
+ }) : (function(o, m, k, k2) {
689
+ if (k2 === void 0) k2 = k;
690
+ o[k2] = m[k];
691
+ }));
692
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
693
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
694
+ };
695
+ Object.defineProperty(exports2, "__esModule", { value: true });
696
+ exports2.menubar = exports2.Menubar = void 0;
697
+ var electron_1 = require("electron");
698
+ var Menubar_1 = require_Menubar();
699
+ Object.defineProperty(exports2, "Menubar", { enumerable: true, get: function() {
700
+ return Menubar_1.Menubar;
701
+ } });
702
+ __exportStar(require_getWindowPosition(), exports2);
703
+ function menubar2(options) {
704
+ return new Menubar_1.Menubar(electron_1.app, options);
705
+ }
706
+ exports2.menubar = menubar2;
707
+ }
708
+ });
709
+
25
710
  // src/main.ts
26
711
  var import_electron = require("electron");
27
712
  var import_node_fs9 = __toESM(require("node:fs"));
28
713
  var import_node_os7 = __toESM(require("node:os"));
29
714
  var import_node_path11 = __toESM(require("node:path"));
30
715
  var import_node_child_process4 = require("node:child_process");
31
- var import_menubar = require("menubar");
716
+ var import_menubar = __toESM(require_lib());
32
717
 
33
718
  // ../shared/src/usage.ts
34
719
  function emptyUsage() {
@@ -1042,7 +1727,17 @@ var import_node_events = require("node:events");
1042
1727
  var import_node_fs = __toESM(require("node:fs"));
1043
1728
  var import_node_os = __toESM(require("node:os"));
1044
1729
  var import_node_path = __toESM(require("node:path"));
1045
- var DEFAULT_DASHBOARD_URL = "https://codex.chenli.dev";
1730
+
1731
+ // src/version.ts
1732
+ var APP_VERSION = true ? "0.2.2" : "0.0.0-dev";
1733
+ var APP_CHANNEL = true ? "prod" : "dev";
1734
+ var IS_DEV_BUILD = APP_CHANNEL === "dev";
1735
+ var APP_NAME = IS_DEV_BUILD ? "Codex Tracker (dev)" : "Codex Tracker";
1736
+
1737
+ // src/core/config.ts
1738
+ var PROD_DASHBOARD_URL = "https://codex.chenli.dev";
1739
+ var DEV_DASHBOARD_URL = "http://localhost:3000";
1740
+ var DEFAULT_DASHBOARD_URL = IS_DEV_BUILD ? DEV_DASHBOARD_URL : PROD_DASHBOARD_URL;
1046
1741
  var SOURCE_FORMATS = ["codex", "pi", "generic", "opencode", "cline"];
1047
1742
  var DEFAULT_SOURCES = { codex: true, pi: true, hermes: true, opencode: true, cline: true, roo: true, kilo: true };
1048
1743
  var SOURCE_IDS = Object.keys(DEFAULT_SOURCES);
@@ -1062,10 +1757,11 @@ var DEFAULT_CONFIG = {
1062
1757
  trackAllProviders: false,
1063
1758
  liveRateLimits: true,
1064
1759
  usageRefreshSec: 60,
1065
- checkUpdates: true
1760
+ // a dev build must never offer to replace itself with the published package
1761
+ checkUpdates: !IS_DEV_BUILD
1066
1762
  };
1067
1763
  function configDir() {
1068
- return process.env.CODEX_TRACKER_HOME || import_node_path.default.join(import_node_os.default.homedir(), ".codex-tracker");
1764
+ return process.env.CODEX_TRACKER_HOME || import_node_path.default.join(import_node_os.default.homedir(), IS_DEV_BUILD ? ".codex-tracker-dev" : ".codex-tracker");
1069
1765
  }
1070
1766
  function readJson(file, fallback) {
1071
1767
  try {
@@ -1116,7 +1812,7 @@ function loadConfig() {
1116
1812
  cfg.sources = normalizeSources(stored.sources);
1117
1813
  cfg.trackAllProviders = stored.trackAllProviders === true;
1118
1814
  cfg.liveRateLimits = stored.liveRateLimits !== false;
1119
- cfg.checkUpdates = stored.checkUpdates !== false;
1815
+ cfg.checkUpdates = typeof stored.checkUpdates === "boolean" ? stored.checkUpdates : DEFAULT_CONFIG.checkUpdates;
1120
1816
  if (!(cfg.usageRefreshSec >= 15)) cfg.usageRefreshSec = DEFAULT_CONFIG.usageRefreshSec;
1121
1817
  if (!["auto", "en", "zh"].includes(cfg.language)) cfg.language = "auto";
1122
1818
  if (!(cfg.uploadIntervalSec >= 10)) cfg.uploadIntervalSec = DEFAULT_CONFIG.uploadIntervalSec;
@@ -1230,6 +1926,7 @@ var en = {
1230
1926
  estimatedPricingHint: "Pricing estimated: model not in the price table",
1231
1927
  other: "Other",
1232
1928
  lastUpload: "Last upload {time}",
1929
+ lastSync: "Last full sync {time}",
1233
1930
  neverUploaded: "Not uploaded yet",
1234
1931
  uploadError: "Upload error: {message}",
1235
1932
  allDevicesToday: "All devices today",
@@ -1248,10 +1945,26 @@ var en = {
1248
1945
  launchAtLogin: "Launch at login",
1249
1946
  refresh: "Refresh",
1250
1947
  quit: "Quit",
1948
+ // full sync
1949
+ syncNow: "Sync now",
1950
+ syncing: "Syncing\u2026",
1951
+ syncTitle: "Rescan every agent on this device and re-upload its full usage history",
1952
+ syncScanning: "Rescanning every agent's transcripts\u2026",
1953
+ syncComputing: "Recomputing token usage\u2026",
1954
+ syncUploading: "Re-uploading this device's history\u2026",
1955
+ syncDownloading: "Fetching other devices\u2026",
1956
+ syncLimits: "Refreshing rate limits\u2026",
1957
+ syncDone: "Synced {agents} \xB7 {files} files \xB7 {sessions} sessions \xB7 {buckets} hours re-uploaded",
1958
+ syncDoneLocal: "Rescanned {agents} \xB7 {files} files \xB7 {sessions} sessions \u2014 sign in to upload",
1959
+ syncFailed: "Sync failed: {message}",
1251
1960
  sessions: "{n} sessions",
1252
1961
  files: "{n} files",
1253
1962
  noData: "No usage found yet. Run Codex and this will fill in.",
1254
1963
  version: "v{version}",
1964
+ // dev builds
1965
+ devBuild: "DEV",
1966
+ devBuildTitle: "Local build \u2014 uploading to {url} (dev environment). Config: {dir}",
1967
+ devBuildMenu: "Dev build \u2192 {url}",
1255
1968
  justNow: "just now",
1256
1969
  never: "never",
1257
1970
  local: "Local",
@@ -1279,6 +1992,7 @@ Commands:
1279
1992
  login Connect this device to your dashboard (--dashboard <url>)
1280
1993
  logout Disconnect this device
1281
1994
  status Print today's usage, live session and rate limits
1995
+ sync Rescan every agent and re-upload this device's full history (calibrate)
1282
1996
  paths Show detected session directories (Codex, pi, OpenCode, Cline/Roo/Kilo, Hermes, custom)
1283
1997
  update Install the newest published version (--check only reports)
1284
1998
  config get|set Read or change settings (config set uploadIntervalSec 30, config set sources.pi false)
@@ -1293,9 +2007,9 @@ Options:
1293
2007
  cliVersion: "codex-token-tracker {version}",
1294
2008
  cliUnknownCommand: "Unknown command: {command}",
1295
2009
  cliNoDisplay: "No display detected \u2014 starting agent mode (run `codex-tracker menubar` on a desktop).",
1296
- cliNoElectron: "Electron is not installed (optional dependency) \u2014 starting agent mode.",
2010
+ cliNoElectron: "No Electron runtime available \u2014 starting agent mode.",
1297
2011
  cliDownloadingElectron: "Electron runtime not found \u2014 downloading it now (about 100 MB, one time)\u2026",
1298
- cliElectronDownloadFailed: "Electron download failed. Retry with `npm install -g codex-token-tracker --allow-scripts=electron` or `npm rebuild -g electron`; the headless `codex-tracker agent` works without it.",
2012
+ 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.",
1299
2013
  cliStartingMenubar: "Starting menu bar app\u2026",
1300
2014
  cliLoginStart: "Connecting this device to {dashboard}",
1301
2015
  cliLoginCode: "Your code: {code}",
@@ -1333,7 +2047,15 @@ Options:
1333
2047
  cliUpdateRunning: "Running: {command}",
1334
2048
  cliUpdateDone: "Updated to {version}. Restart the menu bar app (or rerun the CLI) to use it.",
1335
2049
  cliUpdateFailed: "Update failed (exit {code}). Run it yourself: {command}",
1336
- cliUpdateCheckFailed: "Could not reach the npm registry: {message}"
2050
+ cliUpdateCheckFailed: "Could not reach the npm registry: {message}",
2051
+ cliSyncStart: "Full sync: rescanning every agent on this device\u2026",
2052
+ cliSyncScanned: " scanned {files} files in {roots} directories ({agents})",
2053
+ cliSyncUploaded: " re-uploaded {buckets} hour buckets and {sessions} sessions",
2054
+ cliSyncLocal: " not signed in \u2014 nothing uploaded (run `codex-tracker login` first)",
2055
+ cliSyncDone: "Sync complete in {seconds}s \u2014 {sessions} sessions, today {tokens} tok \xB7 {cost}",
2056
+ cliSyncFailed: "Sync failed: {message}",
2057
+ cliChannelDev: "Dev build \u2014 dashboard {url} \xB7 config {dir}",
2058
+ cliUpdateDevBuild: "This is a local dev build; `update` would install the published package over it. Run `pnpm build` in the repo instead."
1337
2059
  };
1338
2060
 
1339
2061
  // src/i18n/zh.ts
@@ -1381,6 +2103,7 @@ var zh = {
1381
2103
  estimatedPricingHint: "\u4EF7\u683C\u4E3A\u4F30\u7B97\u503C\uFF1A\u8BE5\u6A21\u578B\u4E0D\u5728\u4EF7\u76EE\u8868\u4E2D",
1382
2104
  other: "\u5176\u4ED6",
1383
2105
  lastUpload: "\u4E0A\u6B21\u4E0A\u4F20 {time}",
2106
+ lastSync: "\u4E0A\u6B21\u5B8C\u6574\u540C\u6B65 {time}",
1384
2107
  neverUploaded: "\u5C1A\u672A\u4E0A\u4F20",
1385
2108
  uploadError: "\u4E0A\u4F20\u5931\u8D25\uFF1A{message}",
1386
2109
  allDevicesToday: "\u6240\u6709\u8BBE\u5907\u4ECA\u65E5",
@@ -1399,10 +2122,26 @@ var zh = {
1399
2122
  launchAtLogin: "\u5F00\u673A\u81EA\u542F\u52A8",
1400
2123
  refresh: "\u5237\u65B0",
1401
2124
  quit: "\u9000\u51FA",
2125
+ // 完整同步
2126
+ syncNow: "\u7ACB\u5373\u540C\u6B65",
2127
+ syncing: "\u540C\u6B65\u4E2D\u2026",
2128
+ 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",
2129
+ syncScanning: "\u6B63\u5728\u91CD\u65B0\u626B\u63CF\u6240\u6709\u667A\u80FD\u4F53\u7684\u4F1A\u8BDD\u8BB0\u5F55\u2026",
2130
+ syncComputing: "\u6B63\u5728\u91CD\u65B0\u8BA1\u7B97 token \u7528\u91CF\u2026",
2131
+ syncUploading: "\u6B63\u5728\u91CD\u65B0\u4E0A\u4F20\u672C\u8BBE\u5907\u7684\u5386\u53F2\u6570\u636E\u2026",
2132
+ syncDownloading: "\u6B63\u5728\u62C9\u53D6\u5176\u4ED6\u8BBE\u5907\u7684\u6570\u636E\u2026",
2133
+ syncLimits: "\u6B63\u5728\u5237\u65B0\u9650\u989D\u2026",
2134
+ 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",
2135
+ syncDoneLocal: "\u5DF2\u91CD\u65B0\u626B\u63CF {agents} \xB7 {files} \u4E2A\u6587\u4EF6 \xB7 {sessions} \u4E2A\u4F1A\u8BDD \u2014 \u767B\u5F55\u540E\u53EF\u4E0A\u4F20",
2136
+ syncFailed: "\u540C\u6B65\u5931\u8D25\uFF1A{message}",
1402
2137
  sessions: "{n} \u4E2A\u4F1A\u8BDD",
1403
2138
  files: "{n} \u4E2A\u6587\u4EF6",
1404
2139
  noData: "\u8FD8\u6CA1\u6709\u7528\u91CF\u6570\u636E\u3002\u8FD0\u884C Codex \u540E\u8FD9\u91CC\u4F1A\u81EA\u52A8\u66F4\u65B0\u3002",
1405
2140
  version: "v{version}",
2141
+ // 开发版
2142
+ devBuild: "\u5F00\u53D1\u7248",
2143
+ 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}",
2144
+ devBuildMenu: "\u5F00\u53D1\u7248 \u2192 {url}",
1406
2145
  justNow: "\u521A\u521A",
1407
2146
  never: "\u4ECE\u672A",
1408
2147
  local: "\u672C\u673A",
@@ -1428,6 +2167,7 @@ var zh = {
1428
2167
  login \u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230\u4EEA\u8868\u76D8\uFF08--dashboard <url>\uFF09
1429
2168
  logout \u65AD\u5F00\u672C\u8BBE\u5907
1430
2169
  status \u6253\u5370\u4ECA\u65E5\u7528\u91CF\u3001\u5B9E\u65F6\u4F1A\u8BDD\u4E0E\u9650\u989D
2170
+ 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
1431
2171
  paths \u663E\u793A\u68C0\u6D4B\u5230\u7684\u4F1A\u8BDD\u76EE\u5F55\uFF08Codex\u3001pi\u3001OpenCode\u3001Cline/Roo/Kilo\u3001Hermes\u3001\u81EA\u5B9A\u4E49\uFF09
1432
2172
  update \u5B89\u88C5\u6700\u65B0\u53D1\u5E03\u7248\u672C\uFF08--check \u53EA\u68C0\u67E5\u4E0D\u5B89\u88C5\uFF09
1433
2173
  config get|set \u8BFB\u53D6\u6216\u4FEE\u6539\u8BBE\u7F6E\uFF08config set uploadIntervalSec 30\uFF0Cconfig set sources.pi false\uFF09
@@ -1442,9 +2182,9 @@ var zh = {
1442
2182
  cliVersion: "codex-token-tracker {version}",
1443
2183
  cliUnknownCommand: "\u672A\u77E5\u547D\u4EE4\uFF1A{command}",
1444
2184
  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",
1445
- cliNoElectron: "\u672A\u5B89\u88C5 Electron\uFF08\u53EF\u9009\u4F9D\u8D56\uFF09\u2014 \u4EE5 agent \u6A21\u5F0F\u542F\u52A8\u3002",
2185
+ cliNoElectron: "\u6CA1\u6709\u53EF\u7528\u7684 Electron \u8FD0\u884C\u65F6 \u2014 \u4EE5 agent \u6A21\u5F0F\u542F\u52A8\u3002",
1446
2186
  cliDownloadingElectron: "\u672A\u627E\u5230 Electron \u8FD0\u884C\u65F6\uFF0C\u6B63\u5728\u4E0B\u8F7D\uFF08\u7EA6 100 MB\uFF0C\u4EC5\u9700\u4E00\u6B21\uFF09\u2026",
1447
- cliElectronDownloadFailed: "Electron \u4E0B\u8F7D\u5931\u8D25\u3002\u53EF\u91CD\u8BD5 `npm install -g codex-token-tracker --allow-scripts=electron` \u6216 `npm rebuild -g electron`\uFF1B\u65E0\u754C\u9762\u7684 `codex-tracker agent` \u65E0\u9700 Electron\u3002",
2187
+ 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",
1448
2188
  cliStartingMenubar: "\u6B63\u5728\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u2026",
1449
2189
  cliLoginStart: "\u6B63\u5728\u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230 {dashboard}",
1450
2190
  cliLoginCode: "\u4F60\u7684\u4EE3\u7801\uFF1A{code}",
@@ -1482,7 +2222,15 @@ var zh = {
1482
2222
  cliUpdateRunning: "\u6B63\u5728\u6267\u884C\uFF1A{command}",
1483
2223
  cliUpdateDone: "\u5DF2\u66F4\u65B0\u5230 {version}\u3002\u8BF7\u91CD\u542F\u83DC\u5355\u680F\u5E94\u7528\uFF08\u6216\u91CD\u65B0\u8FD0\u884C CLI\uFF09\u4EE5\u751F\u6548\u3002",
1484
2224
  cliUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF1A{command}",
1485
- cliUpdateCheckFailed: "\u65E0\u6CD5\u8BBF\u95EE npm registry\uFF1A{message}"
2225
+ cliUpdateCheckFailed: "\u65E0\u6CD5\u8BBF\u95EE npm registry\uFF1A{message}",
2226
+ cliSyncStart: "\u5B8C\u6574\u540C\u6B65\uFF1A\u6B63\u5728\u91CD\u65B0\u626B\u63CF\u672C\u8BBE\u5907\u4E0A\u7684\u6240\u6709\u667A\u80FD\u4F53\u2026",
2227
+ cliSyncScanned: " \u5DF2\u626B\u63CF {roots} \u4E2A\u76EE\u5F55\u4E2D\u7684 {files} \u4E2A\u6587\u4EF6\uFF08{agents}\uFF09",
2228
+ cliSyncUploaded: " \u5DF2\u91CD\u65B0\u4E0A\u4F20 {buckets} \u4E2A\u5C0F\u65F6\u6BB5\u4E0E {sessions} \u4E2A\u4F1A\u8BDD",
2229
+ cliSyncLocal: " \u672A\u767B\u5F55 \u2014 \u6CA1\u6709\u4E0A\u4F20\u4EFB\u4F55\u6570\u636E\uFF08\u8BF7\u5148\u8FD0\u884C `codex-tracker login`\uFF09",
2230
+ cliSyncDone: "\u540C\u6B65\u5B8C\u6210\uFF0C\u7528\u65F6 {seconds} \u79D2 \u2014 {sessions} \u4E2A\u4F1A\u8BDD\uFF0C\u4ECA\u65E5 {tokens} tok \xB7 {cost}",
2231
+ cliSyncFailed: "\u540C\u6B65\u5931\u8D25\uFF1A{message}",
2232
+ cliChannelDev: "\u5F00\u53D1\u7248 \u2014\u2014 \u4EEA\u8868\u76D8 {url} \xB7 \u914D\u7F6E\u76EE\u5F55 {dir}",
2233
+ 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"
1486
2234
  };
1487
2235
 
1488
2236
  // src/i18n/index.ts
@@ -2289,6 +3037,15 @@ var SessionStore = class {
2289
3037
  onChange = null;
2290
3038
  debounceTimer = null;
2291
3039
  sessionCache = null;
3040
+ /**
3041
+ * Forget the parsed-file index so the next deep refresh re-reads and re-parses every transcript.
3042
+ * Used by the full sync: files are otherwise skipped while their size and mtime are unchanged, which
3043
+ * would keep stale numbers around after a parser or pricing change.
3044
+ */
3045
+ reset() {
3046
+ this.files.clear();
3047
+ this.sessionCache = null;
3048
+ }
2292
3049
  async refreshDeep() {
2293
3050
  const o = this.getOptions();
2294
3051
  this.roots = discoverSessionRoots({ extraSessionDirs: o.extraSessionDirs, sources: o.sources });
@@ -4086,7 +4843,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require2 !== "undefined" ? requir
4086
4843
  if (typeof require2 !== "undefined") return require2.apply(this, arguments);
4087
4844
  throw Error('Dynamic require of "' + x + '" is not supported');
4088
4845
  });
4089
- var __commonJS = (cb, mod) => function __require2() {
4846
+ var __commonJS2 = (cb, mod) => function __require2() {
4090
4847
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4091
4848
  };
4092
4849
  var __copyProps2 = (to, from, except, desc) => {
@@ -4105,7 +4862,7 @@ var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__
4105
4862
  isNodeMode || !mod || !mod.__esModule ? __defProp6(target, "default", { value: mod, enumerable: true }) : target,
4106
4863
  mod
4107
4864
  ));
4108
- var require_constants = __commonJS({
4865
+ var require_constants = __commonJS2({
4109
4866
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/constants.js"(exports2, module2) {
4110
4867
  "use strict";
4111
4868
  var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
@@ -4126,7 +4883,7 @@ var require_constants = __commonJS({
4126
4883
  };
4127
4884
  }
4128
4885
  });
4129
- var require_node_gyp_build = __commonJS({
4886
+ var require_node_gyp_build = __commonJS2({
4130
4887
  "../node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports2, module2) {
4131
4888
  var fs10 = __require("fs");
4132
4889
  var path12 = __require("path");
@@ -4293,7 +5050,7 @@ var require_node_gyp_build = __commonJS({
4293
5050
  load.compareTuples = compareTuples;
4294
5051
  }
4295
5052
  });
4296
- var require_node_gyp_build2 = __commonJS({
5053
+ var require_node_gyp_build2 = __commonJS2({
4297
5054
  "../node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/index.js"(exports2, module2) {
4298
5055
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
4299
5056
  if (typeof runtimeRequire.addon === "function") {
@@ -4303,7 +5060,7 @@ var require_node_gyp_build2 = __commonJS({
4303
5060
  }
4304
5061
  }
4305
5062
  });
4306
- var require_fallback = __commonJS({
5063
+ var require_fallback = __commonJS2({
4307
5064
  "../node_modules/.pnpm/bufferutil@4.0.9/node_modules/bufferutil/fallback.js"(exports2, module2) {
4308
5065
  "use strict";
4309
5066
  var mask = (source, mask2, output, offset, length) => {
@@ -4320,7 +5077,7 @@ var require_fallback = __commonJS({
4320
5077
  module2.exports = { mask, unmask };
4321
5078
  }
4322
5079
  });
4323
- var require_bufferutil = __commonJS({
5080
+ var require_bufferutil = __commonJS2({
4324
5081
  "../node_modules/.pnpm/bufferutil@4.0.9/node_modules/bufferutil/index.js"(exports2, module2) {
4325
5082
  "use strict";
4326
5083
  try {
@@ -4330,7 +5087,7 @@ var require_bufferutil = __commonJS({
4330
5087
  }
4331
5088
  }
4332
5089
  });
4333
- var require_buffer_util = __commonJS({
5090
+ var require_buffer_util = __commonJS2({
4334
5091
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/buffer-util.js"(exports2, module2) {
4335
5092
  "use strict";
4336
5093
  var { EMPTY_BUFFER } = require_constants();
@@ -4403,7 +5160,7 @@ var require_buffer_util = __commonJS({
4403
5160
  }
4404
5161
  }
4405
5162
  });
4406
- var require_limiter = __commonJS({
5163
+ var require_limiter = __commonJS2({
4407
5164
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/limiter.js"(exports2, module2) {
4408
5165
  "use strict";
4409
5166
  var kDone = /* @__PURE__ */ Symbol("kDone");
@@ -4451,7 +5208,7 @@ var require_limiter = __commonJS({
4451
5208
  module2.exports = Limiter;
4452
5209
  }
4453
5210
  });
4454
- var require_permessage_deflate = __commonJS({
5211
+ var require_permessage_deflate = __commonJS2({
4455
5212
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/permessage-deflate.js"(exports2, module2) {
4456
5213
  "use strict";
4457
5214
  var zlib = __require("zlib");
@@ -4832,7 +5589,7 @@ var require_permessage_deflate = __commonJS({
4832
5589
  }
4833
5590
  }
4834
5591
  });
4835
- var require_validation = __commonJS({
5592
+ var require_validation = __commonJS2({
4836
5593
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/validation.js"(exports2, module2) {
4837
5594
  "use strict";
4838
5595
  var { isUtf8 } = __require("buffer");
@@ -5031,7 +5788,7 @@ var require_validation = __commonJS({
5031
5788
  }
5032
5789
  }
5033
5790
  });
5034
- var require_receiver = __commonJS({
5791
+ var require_receiver = __commonJS2({
5035
5792
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/receiver.js"(exports2, module2) {
5036
5793
  "use strict";
5037
5794
  var { Writable } = __require("stream");
@@ -5661,7 +6418,7 @@ var require_receiver = __commonJS({
5661
6418
  module2.exports = Receiver2;
5662
6419
  }
5663
6420
  });
5664
- var require_sender = __commonJS({
6421
+ var require_sender = __commonJS2({
5665
6422
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/sender.js"(exports2, module2) {
5666
6423
  "use strict";
5667
6424
  var { Duplex } = __require("stream");
@@ -6152,7 +6909,7 @@ var require_sender = __commonJS({
6152
6909
  }
6153
6910
  }
6154
6911
  });
6155
- var require_event_target = __commonJS({
6912
+ var require_event_target = __commonJS2({
6156
6913
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/event-target.js"(exports2, module2) {
6157
6914
  "use strict";
6158
6915
  var { kForOnEventAttribute, kListener } = require_constants();
@@ -6379,7 +7136,7 @@ var require_event_target = __commonJS({
6379
7136
  }
6380
7137
  }
6381
7138
  });
6382
- var require_extension = __commonJS({
7139
+ var require_extension = __commonJS2({
6383
7140
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/extension.js"(exports2, module2) {
6384
7141
  "use strict";
6385
7142
  var { tokenChars } = require_validation();
@@ -6530,7 +7287,7 @@ var require_extension = __commonJS({
6530
7287
  module2.exports = { format, parse };
6531
7288
  }
6532
7289
  });
6533
- var require_websocket = __commonJS({
7290
+ var require_websocket = __commonJS2({
6534
7291
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/websocket.js"(exports2, module2) {
6535
7292
  "use strict";
6536
7293
  var EventEmitter2 = __require("events");
@@ -7424,7 +8181,7 @@ var require_websocket = __commonJS({
7424
8181
  }
7425
8182
  }
7426
8183
  });
7427
- var require_stream = __commonJS({
8184
+ var require_stream = __commonJS2({
7428
8185
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/stream.js"(exports2, module2) {
7429
8186
  "use strict";
7430
8187
  var WebSocket2 = require_websocket();
@@ -7520,7 +8277,7 @@ var require_stream = __commonJS({
7520
8277
  module2.exports = createWebSocketStream2;
7521
8278
  }
7522
8279
  });
7523
- var require_subprotocol = __commonJS({
8280
+ var require_subprotocol = __commonJS2({
7524
8281
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/subprotocol.js"(exports2, module2) {
7525
8282
  "use strict";
7526
8283
  var { tokenChars } = require_validation();
@@ -7563,7 +8320,7 @@ var require_subprotocol = __commonJS({
7563
8320
  module2.exports = { parse };
7564
8321
  }
7565
8322
  });
7566
- var require_websocket_server = __commonJS({
8323
+ var require_websocket_server = __commonJS2({
7567
8324
  "../node_modules/.pnpm/ws@8.21.0_bufferutil@4.0.9/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
7568
8325
  "use strict";
7569
8326
  var EventEmitter2 = __require("events");
@@ -8847,11 +9604,25 @@ var Uploader = class {
8847
9604
  throw err;
8848
9605
  }
8849
9606
  }
8850
- /** Upload changed hour buckets and session summaries. Returns counts of pushed items. */
8851
- async pushAll(buckets, sessions, sessionCosts) {
9607
+ /** Wait for an in-flight incremental push to finish so a full sync isn't skipped by the guard. */
9608
+ async awaitIdle(timeoutMs = 15e3) {
9609
+ const deadline = Date.now() + timeoutMs;
9610
+ while (this.inFlight && Date.now() < deadline) await new Promise((r) => setTimeout(r, 100));
9611
+ }
9612
+ /**
9613
+ * Upload changed hour buckets and session summaries. Returns counts of pushed items.
9614
+ * With `full`, the record of what was already pushed is dropped first so *everything* is re-sent —
9615
+ * that is what recalibrates this device's totals on the dashboard after a parser or pricing change.
9616
+ */
9617
+ async pushAll(buckets, sessions, sessionCosts, opts = {}) {
9618
+ if (opts.full) await this.awaitIdle();
8852
9619
  if (this.inFlight) return { buckets: 0, sessions: 0 };
8853
9620
  this.inFlight = true;
8854
9621
  try {
9622
+ if (opts.full) {
9623
+ this.state.pushedBuckets = {};
9624
+ this.state.pushedSessions = {};
9625
+ }
8855
9626
  const changed = [];
8856
9627
  const hashes = /* @__PURE__ */ new Map();
8857
9628
  for (const b of buckets.sort((a, b2) => a.hourStart - b2.hourStart)) {
@@ -9061,13 +9832,7 @@ async function fetchLiveRateLimits(appVersion, timeoutMs = 1e4) {
9061
9832
  var import_node_fs8 = __toESM(require("node:fs"));
9062
9833
  var import_node_path10 = __toESM(require("node:path"));
9063
9834
  var import_node_child_process3 = require("node:child_process");
9064
-
9065
- // src/version.ts
9066
- var APP_VERSION = true ? "0.2.0" : "0.0.0-dev";
9067
- var APP_NAME = "Codex Tracker";
9068
-
9069
- // src/core/update.ts
9070
- var NPM_PACKAGE = "codex-token-tracker";
9835
+ var NPM_PACKAGE = true ? "codex-token-tracker" : "codex-token-tracker";
9071
9836
  var CHECK_TTL_MS = 6 * 60 * 60 * 1e3;
9072
9837
  var REQUEST_TIMEOUT_MS = 8e3;
9073
9838
  var INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
@@ -9090,7 +9855,7 @@ function compareVersions(a, b) {
9090
9855
  return A.pre < B.pre ? -1 : 1;
9091
9856
  }
9092
9857
  function isDevBuild(version2) {
9093
- return version2.startsWith("0.0.0");
9858
+ return IS_DEV_BUILD || version2.startsWith("0.0.0");
9094
9859
  }
9095
9860
  function registryBase() {
9096
9861
  const r = process.env.CODEX_TRACKER_REGISTRY || process.env.npm_config_registry || process.env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org";
@@ -9141,7 +9906,8 @@ function updateCommand(pm, spec) {
9141
9906
  return `${pm} ${updateArgs(pm, spec).join(" ")}`;
9142
9907
  }
9143
9908
  function cachePath() {
9144
- return import_node_path10.default.join(configDir(), "update.json");
9909
+ const suffix = NPM_PACKAGE === "codex-token-tracker" ? "" : `-${NPM_PACKAGE}`;
9910
+ return import_node_path10.default.join(configDir(), `update${suffix}.json`);
9145
9911
  }
9146
9912
  function readCache() {
9147
9913
  try {
@@ -9233,6 +9999,7 @@ var REMOTE_MS = 6e4;
9233
9999
  var LIVE_LIMITS_DEBOUNCE_MS = 1e4;
9234
10000
  var LIVE_LIMITS_MIN_GAP_MS = 2e4;
9235
10001
  var UPDATE_CHECK_MS = 6 * 60 * 60 * 1e3;
10002
+ var SYNC_BANNER_MS = 25e3;
9236
10003
  var Engine = class extends import_node_events.EventEmitter {
9237
10004
  constructor(opts) {
9238
10005
  super();
@@ -9272,6 +10039,13 @@ var Engine = class extends import_node_events.EventEmitter {
9272
10039
  update = null;
9273
10040
  updateStatus = "idle";
9274
10041
  updateLog = null;
10042
+ syncStatus = "idle";
10043
+ syncPhase = null;
10044
+ syncStartedAt = null;
10045
+ syncFinishedAt = null;
10046
+ syncError = null;
10047
+ syncResult = null;
10048
+ syncBannerTimer = null;
9275
10049
  get heatmapWeeks() {
9276
10050
  return this.opts.heatmapWeeks ?? 16;
9277
10051
  }
@@ -9322,6 +10096,8 @@ var Engine = class extends import_node_events.EventEmitter {
9322
10096
  this.timers = [];
9323
10097
  if (this.liveLimitsTimer) clearTimeout(this.liveLimitsTimer);
9324
10098
  this.liveLimitsTimer = null;
10099
+ if (this.syncBannerTimer) clearTimeout(this.syncBannerTimer);
10100
+ this.syncBannerTimer = null;
9325
10101
  this.store.stopWatching();
9326
10102
  this.pending?.abort.abort();
9327
10103
  }
@@ -9425,6 +10201,112 @@ var Engine = class extends import_node_events.EventEmitter {
9425
10201
  await this.uploader.fetchRemote(this.heatmapWeeks);
9426
10202
  this.recompute();
9427
10203
  }
10204
+ /**
10205
+ * Full sync ("calibrate this device"). Unlike the periodic incremental upload this deliberately
10206
+ * throws away every cache on the way:
10207
+ * 1. re-read the config so sources enabled since start-up are picked up,
10208
+ * 2. drop the parsed-file index and re-discover + re-parse every transcript of every agent
10209
+ * (Codex plus the agents running on the Codex subscription: pi, OpenCode, Cline/Roo/Kilo,
10210
+ * Hermes and any custom `extraSessionDirs`),
10211
+ * 3. recompute the aggregates with the current pricing table,
10212
+ * 4. re-upload *everything* — not just what changed — so the dashboard's totals for this device
10213
+ * are replaced by the freshly computed ones,
10214
+ * 5. pull the other devices' rows and the live rate limits back down.
10215
+ *
10216
+ * Returns null when a sync is already running. Never throws: failures land in the returned state.
10217
+ */
10218
+ async syncNow() {
10219
+ if (this.syncStatus === "running") return null;
10220
+ if (this.syncBannerTimer) {
10221
+ clearTimeout(this.syncBannerTimer);
10222
+ this.syncBannerTimer = null;
10223
+ }
10224
+ const startedAt = Date.now();
10225
+ this.syncStatus = "running";
10226
+ this.syncPhase = null;
10227
+ this.syncStartedAt = startedAt;
10228
+ this.syncFinishedAt = null;
10229
+ this.syncError = null;
10230
+ const phase = (p) => {
10231
+ this.syncPhase = p;
10232
+ this.emitSnapshot();
10233
+ };
10234
+ try {
10235
+ phase("scanning");
10236
+ this.reloadConfig();
10237
+ this.store.reset();
10238
+ await this.store.refreshDeep();
10239
+ phase("computing");
10240
+ this.recompute();
10241
+ let uploadedBuckets = 0;
10242
+ let uploadedSessions = 0;
10243
+ const uploaded = this.opts.upload && this.signedIn;
10244
+ if (uploaded) {
10245
+ phase("uploading");
10246
+ const r = await this.uploader.pushAll(this.stats.buckets, this.stats.sessions, this.stats.sessionCosts, { full: true });
10247
+ uploadedBuckets = r.buckets;
10248
+ uploadedSessions = r.sessions;
10249
+ await this.heartbeatNow().catch(() => {
10250
+ });
10251
+ phase("downloading");
10252
+ await this.fetchRemoteNow();
10253
+ }
10254
+ phase("limits");
10255
+ await this.refreshLiveLimits(true);
10256
+ const finishedAt = Date.now();
10257
+ this.syncResult = {
10258
+ startedAt,
10259
+ finishedAt,
10260
+ durationMs: finishedAt - startedAt,
10261
+ files: this.store.fileCount,
10262
+ sessions: this.store.sessions().length,
10263
+ roots: this.store.roots.length,
10264
+ agents: [...new Set(this.store.roots.map((r) => r.agent))].sort(),
10265
+ uploadedBuckets,
10266
+ uploadedSessions,
10267
+ uploaded
10268
+ };
10269
+ this.syncStatus = "done";
10270
+ this.syncFinishedAt = finishedAt;
10271
+ this.opts.log?.(
10272
+ `sync: ${this.syncResult.files} files, ${this.syncResult.sessions} sessions, ${uploadedBuckets} buckets / ${uploadedSessions} sessions uploaded in ${this.syncResult.durationMs} ms`
10273
+ );
10274
+ return this.syncResult;
10275
+ } catch (err) {
10276
+ this.syncStatus = "error";
10277
+ this.syncFinishedAt = Date.now();
10278
+ this.syncError = err instanceof SignedOutError ? "not signed in" : errorMessage(err);
10279
+ this.opts.log?.(`sync failed: ${this.syncError}`);
10280
+ return null;
10281
+ } finally {
10282
+ this.syncPhase = null;
10283
+ this.emitSnapshot();
10284
+ this.scheduleSyncBannerClear();
10285
+ }
10286
+ }
10287
+ /** Return the sync banner to "idle" a little after it finished, keeping `last` for the footer. */
10288
+ scheduleSyncBannerClear() {
10289
+ if (this.opts.watch === false) return;
10290
+ if (this.syncBannerTimer) clearTimeout(this.syncBannerTimer);
10291
+ this.syncBannerTimer = setTimeout(() => {
10292
+ this.syncBannerTimer = null;
10293
+ if (this.syncStatus === "done" || this.syncStatus === "error") {
10294
+ this.syncStatus = "idle";
10295
+ this.emitSnapshot();
10296
+ }
10297
+ }, SYNC_BANNER_MS);
10298
+ this.syncBannerTimer.unref?.();
10299
+ }
10300
+ syncState() {
10301
+ return {
10302
+ status: this.syncStatus,
10303
+ phase: this.syncPhase,
10304
+ startedAt: this.syncStartedAt,
10305
+ finishedAt: this.syncFinishedAt,
10306
+ error: this.syncError,
10307
+ last: this.syncResult
10308
+ };
10309
+ }
9428
10310
  /**
9429
10311
  * Ask the npm registry for the newest published version (cached for 6 h unless `force`).
9430
10312
  * Best-effort: a failure is recorded on the snapshot, never thrown.
@@ -9542,6 +10424,7 @@ ${r.output}`.trim();
9542
10424
  const sessions = this.store.sessions();
9543
10425
  return {
9544
10426
  version: APP_VERSION,
10427
+ channel: APP_CHANNEL,
9545
10428
  generatedAt: Date.now(),
9546
10429
  language: this.language(),
9547
10430
  languageSetting: this.config.language,
@@ -9560,6 +10443,7 @@ ${r.output}`.trim();
9560
10443
  rateLimitsError: this.config.liveRateLimits ? this.liveLimitsError : null,
9561
10444
  rateLimitsUpdatedAt: this.liveLimits ? this.liveLimitsAt : s.logRateLimits?.observedAt ?? null,
9562
10445
  update: this.updateState(),
10446
+ sync: this.syncState(),
9563
10447
  modelsToday: s.modelsToday,
9564
10448
  modelsMonth: s.modelsMonth,
9565
10449
  byAgentToday: s.byAgentToday,
@@ -9576,6 +10460,7 @@ ${r.output}`.trim();
9576
10460
  },
9577
10461
  sessionDirs: SessionStore.rootDirs(this.store.roots),
9578
10462
  sessionRoots: this.store.roots.map((r) => ({ dir: r.dir, agent: r.agent, format: r.format, origin: r.origin })),
10463
+ configDir: configDir(),
9579
10464
  launchAtLogin: this.config.launchAtLogin,
9580
10465
  trayTitle: this.config.trayTitle
9581
10466
  };
@@ -9622,14 +10507,15 @@ function updateTray(s) {
9622
10507
  mb.tray.setToolTip(`${APP_NAME} \u2014 ${t(L, "today")}: ${formatTokens(s.today.usage.total)} \xB7 ${formatUSD(s.today.cost)}${live}`);
9623
10508
  if (isMac) mb.tray.setTitle(trayText(s), { fontType: "monospacedDigit" });
9624
10509
  }
9625
- var launchAgentPath = import_node_path11.default.join(import_node_os7.default.homedir(), "Library", "LaunchAgents", "dev.codex-tracker.menubar.plist");
10510
+ var launchAgentLabel = IS_DEV_BUILD ? "dev.codex-tracker.menubar.dev" : "dev.codex-tracker.menubar";
10511
+ var launchAgentPath = import_node_path11.default.join(import_node_os7.default.homedir(), "Library", "LaunchAgents", `${launchAgentLabel}.plist`);
9626
10512
  function setLaunchAtLogin(enabled) {
9627
10513
  if (isMac) {
9628
10514
  if (enabled) {
9629
10515
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
9630
10516
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
9631
10517
  <plist version="1.0"><dict>
9632
- <key>Label</key><string>dev.codex-tracker.menubar</string>
10518
+ <key>Label</key><string>${launchAgentLabel}</string>
9633
10519
  <key>ProgramArguments</key><array><string>${process.execPath}</string><string>${import_node_path11.default.join(__dirname, "main.js")}</string></array>
9634
10520
  <key>RunAtLoad</key><true/>
9635
10521
  <key>KeepAlive</key><false/>
@@ -9664,6 +10550,7 @@ function buildMenu(s) {
9664
10550
  });
9665
10551
  const template = [
9666
10552
  { label: `${APP_NAME} ${t(L, "version", { version: APP_VERSION })}`, enabled: false },
10553
+ ...IS_DEV_BUILD ? [{ label: t(L, "devBuildMenu", { url: s.auth.dashboardUrl }), enabled: false }] : [],
9667
10554
  {
9668
10555
  label: s.auth.status === "signedIn" ? t(L, "signedInAs", { name: s.auth.user?.name || s.auth.user?.email || "?" }) : t(L, "signedOut"),
9669
10556
  enabled: false
@@ -9688,6 +10575,11 @@ function buildMenu(s) {
9688
10575
  }
9689
10576
  ] : [],
9690
10577
  { label: t(L, "refresh"), click: () => void engine?.refresh(true) },
10578
+ {
10579
+ label: s.sync.status === "running" ? t(L, "syncing") : t(L, "syncNow"),
10580
+ enabled: s.sync.status !== "running",
10581
+ click: () => void engine?.syncNow()
10582
+ },
9691
10583
  ...s.update ? [
9692
10584
  s.update.available ? { label: t(L, "updateAvailable", { version: s.update.latest ?? "?" }), click: () => void engine?.installUpdate() } : { label: t(L, "checkForUpdates"), click: () => void engine?.checkUpdate(true) }
9693
10585
  ] : [],
@@ -9704,10 +10596,10 @@ async function startLogin() {
9704
10596
  log2("login failed", errorMessage(err));
9705
10597
  }
9706
10598
  }
10599
+ import_electron.app.setName(APP_NAME);
9707
10600
  if (!import_electron.app.requestSingleInstanceLock()) {
9708
10601
  import_electron.app.quit();
9709
10602
  } else {
9710
- import_electron.app.setName(APP_NAME);
9711
10603
  import_electron.app.on("second-instance", () => mb?.showWindow());
9712
10604
  import_electron.app.on("window-all-closed", () => {
9713
10605
  });
@@ -9747,6 +10639,7 @@ if (!import_electron.app.requestSingleInstanceLock()) {
9747
10639
  import_electron.ipcMain.handle("auth:cancel", () => engine?.cancelLogin());
9748
10640
  import_electron.ipcMain.handle("auth:logout", () => engine?.logout());
9749
10641
  import_electron.ipcMain.handle("refresh", () => engine?.refresh(true).then(() => void 0));
10642
+ import_electron.ipcMain.handle("sync:now", () => engine?.syncNow().then(() => void 0));
9750
10643
  import_electron.ipcMain.handle("update:check", () => engine?.checkUpdate(true).then(() => void 0));
9751
10644
  import_electron.ipcMain.handle("update:install", () => engine?.installUpdate().then(() => void 0));
9752
10645
  import_electron.ipcMain.handle("quit", () => import_electron.app.quit());