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/preload.js CHANGED
@@ -16,6 +16,7 @@ var bridge = {
16
16
  cancelLogin: () => import_electron.ipcRenderer.invoke("auth:cancel"),
17
17
  logout: () => import_electron.ipcRenderer.invoke("auth:logout"),
18
18
  refresh: () => import_electron.ipcRenderer.invoke("refresh"),
19
+ syncNow: () => import_electron.ipcRenderer.invoke("sync:now"),
19
20
  checkUpdate: () => import_electron.ipcRenderer.invoke("update:check"),
20
21
  installUpdate: () => import_electron.ipcRenderer.invoke("update:install"),
21
22
  quit: () => import_electron.ipcRenderer.invoke("quit")
@@ -12973,6 +12973,7 @@
12973
12973
  estimatedPricingHint: "Pricing estimated: model not in the price table",
12974
12974
  other: "Other",
12975
12975
  lastUpload: "Last upload {time}",
12976
+ lastSync: "Last full sync {time}",
12976
12977
  neverUploaded: "Not uploaded yet",
12977
12978
  uploadError: "Upload error: {message}",
12978
12979
  allDevicesToday: "All devices today",
@@ -12991,10 +12992,26 @@
12991
12992
  launchAtLogin: "Launch at login",
12992
12993
  refresh: "Refresh",
12993
12994
  quit: "Quit",
12995
+ // full sync
12996
+ syncNow: "Sync now",
12997
+ syncing: "Syncing\u2026",
12998
+ syncTitle: "Rescan every agent on this device and re-upload its full usage history",
12999
+ syncScanning: "Rescanning every agent's transcripts\u2026",
13000
+ syncComputing: "Recomputing token usage\u2026",
13001
+ syncUploading: "Re-uploading this device's history\u2026",
13002
+ syncDownloading: "Fetching other devices\u2026",
13003
+ syncLimits: "Refreshing rate limits\u2026",
13004
+ syncDone: "Synced {agents} \xB7 {files} files \xB7 {sessions} sessions \xB7 {buckets} hours re-uploaded",
13005
+ syncDoneLocal: "Rescanned {agents} \xB7 {files} files \xB7 {sessions} sessions \u2014 sign in to upload",
13006
+ syncFailed: "Sync failed: {message}",
12994
13007
  sessions: "{n} sessions",
12995
13008
  files: "{n} files",
12996
13009
  noData: "No usage found yet. Run Codex and this will fill in.",
12997
13010
  version: "v{version}",
13011
+ // dev builds
13012
+ devBuild: "DEV",
13013
+ devBuildTitle: "Local build \u2014 uploading to {url} (dev environment). Config: {dir}",
13014
+ devBuildMenu: "Dev build \u2192 {url}",
12998
13015
  justNow: "just now",
12999
13016
  never: "never",
13000
13017
  local: "Local",
@@ -13022,6 +13039,7 @@ Commands:
13022
13039
  login Connect this device to your dashboard (--dashboard <url>)
13023
13040
  logout Disconnect this device
13024
13041
  status Print today's usage, live session and rate limits
13042
+ sync Rescan every agent and re-upload this device's full history (calibrate)
13025
13043
  paths Show detected session directories (Codex, pi, OpenCode, Cline/Roo/Kilo, Hermes, custom)
13026
13044
  update Install the newest published version (--check only reports)
13027
13045
  config get|set Read or change settings (config set uploadIntervalSec 30, config set sources.pi false)
@@ -13036,9 +13054,9 @@ Options:
13036
13054
  cliVersion: "codex-token-tracker {version}",
13037
13055
  cliUnknownCommand: "Unknown command: {command}",
13038
13056
  cliNoDisplay: "No display detected \u2014 starting agent mode (run `codex-tracker menubar` on a desktop).",
13039
- cliNoElectron: "Electron is not installed (optional dependency) \u2014 starting agent mode.",
13057
+ cliNoElectron: "No Electron runtime available \u2014 starting agent mode.",
13040
13058
  cliDownloadingElectron: "Electron runtime not found \u2014 downloading it now (about 100 MB, one time)\u2026",
13041
- 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.",
13059
+ 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.",
13042
13060
  cliStartingMenubar: "Starting menu bar app\u2026",
13043
13061
  cliLoginStart: "Connecting this device to {dashboard}",
13044
13062
  cliLoginCode: "Your code: {code}",
@@ -13076,7 +13094,15 @@ Options:
13076
13094
  cliUpdateRunning: "Running: {command}",
13077
13095
  cliUpdateDone: "Updated to {version}. Restart the menu bar app (or rerun the CLI) to use it.",
13078
13096
  cliUpdateFailed: "Update failed (exit {code}). Run it yourself: {command}",
13079
- cliUpdateCheckFailed: "Could not reach the npm registry: {message}"
13097
+ cliUpdateCheckFailed: "Could not reach the npm registry: {message}",
13098
+ cliSyncStart: "Full sync: rescanning every agent on this device\u2026",
13099
+ cliSyncScanned: " scanned {files} files in {roots} directories ({agents})",
13100
+ cliSyncUploaded: " re-uploaded {buckets} hour buckets and {sessions} sessions",
13101
+ cliSyncLocal: " not signed in \u2014 nothing uploaded (run `codex-tracker login` first)",
13102
+ cliSyncDone: "Sync complete in {seconds}s \u2014 {sessions} sessions, today {tokens} tok \xB7 {cost}",
13103
+ cliSyncFailed: "Sync failed: {message}",
13104
+ cliChannelDev: "Dev build \u2014 dashboard {url} \xB7 config {dir}",
13105
+ cliUpdateDevBuild: "This is a local dev build; `update` would install the published package over it. Run `pnpm build` in the repo instead."
13080
13106
  };
13081
13107
 
13082
13108
  // src/i18n/zh.ts
@@ -13124,6 +13150,7 @@ Options:
13124
13150
  estimatedPricingHint: "\u4EF7\u683C\u4E3A\u4F30\u7B97\u503C\uFF1A\u8BE5\u6A21\u578B\u4E0D\u5728\u4EF7\u76EE\u8868\u4E2D",
13125
13151
  other: "\u5176\u4ED6",
13126
13152
  lastUpload: "\u4E0A\u6B21\u4E0A\u4F20 {time}",
13153
+ lastSync: "\u4E0A\u6B21\u5B8C\u6574\u540C\u6B65 {time}",
13127
13154
  neverUploaded: "\u5C1A\u672A\u4E0A\u4F20",
13128
13155
  uploadError: "\u4E0A\u4F20\u5931\u8D25\uFF1A{message}",
13129
13156
  allDevicesToday: "\u6240\u6709\u8BBE\u5907\u4ECA\u65E5",
@@ -13142,10 +13169,26 @@ Options:
13142
13169
  launchAtLogin: "\u5F00\u673A\u81EA\u542F\u52A8",
13143
13170
  refresh: "\u5237\u65B0",
13144
13171
  quit: "\u9000\u51FA",
13172
+ // 完整同步
13173
+ syncNow: "\u7ACB\u5373\u540C\u6B65",
13174
+ syncing: "\u540C\u6B65\u4E2D\u2026",
13175
+ 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",
13176
+ syncScanning: "\u6B63\u5728\u91CD\u65B0\u626B\u63CF\u6240\u6709\u667A\u80FD\u4F53\u7684\u4F1A\u8BDD\u8BB0\u5F55\u2026",
13177
+ syncComputing: "\u6B63\u5728\u91CD\u65B0\u8BA1\u7B97 token \u7528\u91CF\u2026",
13178
+ syncUploading: "\u6B63\u5728\u91CD\u65B0\u4E0A\u4F20\u672C\u8BBE\u5907\u7684\u5386\u53F2\u6570\u636E\u2026",
13179
+ syncDownloading: "\u6B63\u5728\u62C9\u53D6\u5176\u4ED6\u8BBE\u5907\u7684\u6570\u636E\u2026",
13180
+ syncLimits: "\u6B63\u5728\u5237\u65B0\u9650\u989D\u2026",
13181
+ 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",
13182
+ syncDoneLocal: "\u5DF2\u91CD\u65B0\u626B\u63CF {agents} \xB7 {files} \u4E2A\u6587\u4EF6 \xB7 {sessions} \u4E2A\u4F1A\u8BDD \u2014 \u767B\u5F55\u540E\u53EF\u4E0A\u4F20",
13183
+ syncFailed: "\u540C\u6B65\u5931\u8D25\uFF1A{message}",
13145
13184
  sessions: "{n} \u4E2A\u4F1A\u8BDD",
13146
13185
  files: "{n} \u4E2A\u6587\u4EF6",
13147
13186
  noData: "\u8FD8\u6CA1\u6709\u7528\u91CF\u6570\u636E\u3002\u8FD0\u884C Codex \u540E\u8FD9\u91CC\u4F1A\u81EA\u52A8\u66F4\u65B0\u3002",
13148
13187
  version: "v{version}",
13188
+ // 开发版
13189
+ devBuild: "\u5F00\u53D1\u7248",
13190
+ 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}",
13191
+ devBuildMenu: "\u5F00\u53D1\u7248 \u2192 {url}",
13149
13192
  justNow: "\u521A\u521A",
13150
13193
  never: "\u4ECE\u672A",
13151
13194
  local: "\u672C\u673A",
@@ -13171,6 +13214,7 @@ Options:
13171
13214
  login \u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230\u4EEA\u8868\u76D8\uFF08--dashboard <url>\uFF09
13172
13215
  logout \u65AD\u5F00\u672C\u8BBE\u5907
13173
13216
  status \u6253\u5370\u4ECA\u65E5\u7528\u91CF\u3001\u5B9E\u65F6\u4F1A\u8BDD\u4E0E\u9650\u989D
13217
+ 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
13174
13218
  paths \u663E\u793A\u68C0\u6D4B\u5230\u7684\u4F1A\u8BDD\u76EE\u5F55\uFF08Codex\u3001pi\u3001OpenCode\u3001Cline/Roo/Kilo\u3001Hermes\u3001\u81EA\u5B9A\u4E49\uFF09
13175
13219
  update \u5B89\u88C5\u6700\u65B0\u53D1\u5E03\u7248\u672C\uFF08--check \u53EA\u68C0\u67E5\u4E0D\u5B89\u88C5\uFF09
13176
13220
  config get|set \u8BFB\u53D6\u6216\u4FEE\u6539\u8BBE\u7F6E\uFF08config set uploadIntervalSec 30\uFF0Cconfig set sources.pi false\uFF09
@@ -13185,9 +13229,9 @@ Options:
13185
13229
  cliVersion: "codex-token-tracker {version}",
13186
13230
  cliUnknownCommand: "\u672A\u77E5\u547D\u4EE4\uFF1A{command}",
13187
13231
  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",
13188
- cliNoElectron: "\u672A\u5B89\u88C5 Electron\uFF08\u53EF\u9009\u4F9D\u8D56\uFF09\u2014 \u4EE5 agent \u6A21\u5F0F\u542F\u52A8\u3002",
13232
+ cliNoElectron: "\u6CA1\u6709\u53EF\u7528\u7684 Electron \u8FD0\u884C\u65F6 \u2014 \u4EE5 agent \u6A21\u5F0F\u542F\u52A8\u3002",
13189
13233
  cliDownloadingElectron: "\u672A\u627E\u5230 Electron \u8FD0\u884C\u65F6\uFF0C\u6B63\u5728\u4E0B\u8F7D\uFF08\u7EA6 100 MB\uFF0C\u4EC5\u9700\u4E00\u6B21\uFF09\u2026",
13190
- 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",
13234
+ 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",
13191
13235
  cliStartingMenubar: "\u6B63\u5728\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u2026",
13192
13236
  cliLoginStart: "\u6B63\u5728\u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230 {dashboard}",
13193
13237
  cliLoginCode: "\u4F60\u7684\u4EE3\u7801\uFF1A{code}",
@@ -13225,7 +13269,15 @@ Options:
13225
13269
  cliUpdateRunning: "\u6B63\u5728\u6267\u884C\uFF1A{command}",
13226
13270
  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",
13227
13271
  cliUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF1A{command}",
13228
- cliUpdateCheckFailed: "\u65E0\u6CD5\u8BBF\u95EE npm registry\uFF1A{message}"
13272
+ cliUpdateCheckFailed: "\u65E0\u6CD5\u8BBF\u95EE npm registry\uFF1A{message}",
13273
+ cliSyncStart: "\u5B8C\u6574\u540C\u6B65\uFF1A\u6B63\u5728\u91CD\u65B0\u626B\u63CF\u672C\u8BBE\u5907\u4E0A\u7684\u6240\u6709\u667A\u80FD\u4F53\u2026",
13274
+ cliSyncScanned: " \u5DF2\u626B\u63CF {roots} \u4E2A\u76EE\u5F55\u4E2D\u7684 {files} \u4E2A\u6587\u4EF6\uFF08{agents}\uFF09",
13275
+ cliSyncUploaded: " \u5DF2\u91CD\u65B0\u4E0A\u4F20 {buckets} \u4E2A\u5C0F\u65F6\u6BB5\u4E0E {sessions} \u4E2A\u4F1A\u8BDD",
13276
+ cliSyncLocal: " \u672A\u767B\u5F55 \u2014 \u6CA1\u6709\u4E0A\u4F20\u4EFB\u4F55\u6570\u636E\uFF08\u8BF7\u5148\u8FD0\u884C `codex-tracker login`\uFF09",
13277
+ cliSyncDone: "\u540C\u6B65\u5B8C\u6210\uFF0C\u7528\u65F6 {seconds} \u79D2 \u2014 {sessions} \u4E2A\u4F1A\u8BDD\uFF0C\u4ECA\u65E5 {tokens} tok \xB7 {cost}",
13278
+ cliSyncFailed: "\u540C\u6B65\u5931\u8D25\uFF1A{message}",
13279
+ cliChannelDev: "\u5F00\u53D1\u7248 \u2014\u2014 \u4EEA\u8868\u76D8 {url} \xB7 \u914D\u7F6E\u76EE\u5F55 {dir}",
13280
+ 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"
13229
13281
  };
13230
13282
 
13231
13283
  // src/i18n/index.ts
@@ -13489,6 +13541,40 @@ Options:
13489
13541
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "primary", onClick: () => void bridge().installUpdate(), children: t2("update") })
13490
13542
  ] });
13491
13543
  }
13544
+ function SyncIcon({ spinning }) {
13545
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { className: `sync-icon${spinning ? " spinning" : ""}`, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", "aria-hidden": true, children: [
13546
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M13.5 8a5.5 5.5 0 1 1-1.61-3.89" }),
13547
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M13.6 2.2v2.9h-2.9" })
13548
+ ] });
13549
+ }
13550
+ var PHASE_KEY = {
13551
+ scanning: "syncScanning",
13552
+ computing: "syncComputing",
13553
+ uploading: "syncUploading",
13554
+ downloading: "syncDownloading",
13555
+ limits: "syncLimits"
13556
+ };
13557
+ function SyncBar({ sync, t: t2 }) {
13558
+ if (sync.status === "idle") return null;
13559
+ if (sync.status === "running") {
13560
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sync-bar", children: [
13561
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SyncIcon, { spinning: true }),
13562
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "msg", children: t2(sync.phase ? PHASE_KEY[sync.phase] : "syncing") })
13563
+ ] });
13564
+ }
13565
+ if (sync.status === "error") {
13566
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "sync-bar failed", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "msg", title: sync.error ?? "", children: t2("syncFailed", { message: sync.error ?? "?" }) }) });
13567
+ }
13568
+ const r = sync.last;
13569
+ if (!r) return null;
13570
+ const params = {
13571
+ agents: r.agents.length ? r.agents.join(", ") : "\u2014",
13572
+ files: r.files,
13573
+ sessions: r.sessions,
13574
+ buckets: r.uploadedBuckets
13575
+ };
13576
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "sync-bar done", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "msg", title: `${r.roots} dirs \xB7 ${(r.durationMs / 1e3).toFixed(1)}s`, children: t2(r.uploaded ? "syncDone" : "syncDoneLocal", params) }) });
13577
+ }
13492
13578
  function App() {
13493
13579
  const [snap, setSnap] = (0, import_react2.useState)(null);
13494
13580
  const [, setClock] = (0, import_react2.useState)(0);
@@ -13519,18 +13605,31 @@ Options:
13519
13605
  const agents = agentsPeriod === "today" ? snap.byAgentToday : snap.byAgentMonth;
13520
13606
  const auth = snap.auth;
13521
13607
  const rl = snap.rateLimits;
13608
+ const syncing = snap.sync.status === "running";
13522
13609
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app", children: [
13523
13610
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "header", children: [
13524
13611
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "brand", children: [
13525
13612
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Logo, {}),
13526
13613
  t2("appName")
13527
13614
  ] }),
13615
+ snap.channel === "dev" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "badge-dev", title: t2("devBuildTitle", { url: auth.dashboardUrl, dir: snap.configDir }), children: t2("devBuild") }) : null,
13528
13616
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "spacer" }),
13529
13617
  live ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "pill live", title: live.projectName ?? "", children: [
13530
13618
  live.tokensPerSecond.toFixed(1),
13531
13619
  " ",
13532
13620
  t2("tokensPerSec")
13533
13621
  ] }) : null,
13622
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
13623
+ "button",
13624
+ {
13625
+ className: "ghost icon-btn",
13626
+ onClick: () => void bridge().syncNow(),
13627
+ disabled: syncing,
13628
+ title: `${t2("syncTitle")}${snap.sync.last ? ` \xB7 ${t2("lastSync", { time: relativeTime(L, snap.sync.last.finishedAt, now) })}` : ""}`,
13629
+ "aria-label": t2("syncNow"),
13630
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SyncIcon, { spinning: syncing })
13631
+ }
13632
+ ),
13534
13633
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "seg", role: "group", "aria-label": t2("language"), children: [
13535
13634
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: L === "en" ? "active" : "", onClick: () => void bridge().setLanguage("en"), children: "EN" }),
13536
13635
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: L === "zh" ? "active" : "", onClick: () => void bridge().setLanguage("zh"), children: "\u4E2D\u6587" })
@@ -13538,6 +13637,7 @@ Options:
13538
13637
  ] }),
13539
13638
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "scroll", children: [
13540
13639
  snap.update ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UpdateBar, { update: snap.update, t: t2 }) : null,
13640
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SyncBar, { sync: snap.sync, t: t2 }),
13541
13641
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "card", children: [
13542
13642
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "card-title", children: [
13543
13643
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t2("today") }),
@@ -13736,7 +13836,14 @@ Options:
13736
13836
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "empty", style: { flex: 1 }, children: t2("signedOut") }),
13737
13837
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "primary", onClick: () => void bridge().login(), children: t2("signIn") })
13738
13838
  ] }),
13739
- auth.error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "error", children: auth.error }) : null
13839
+ auth.error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "error", children: auth.error }) : null,
13840
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "row sync-row", children: [
13841
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { className: "ghost sync-btn", onClick: () => void bridge().syncNow(), disabled: syncing, title: t2("syncTitle"), children: [
13842
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SyncIcon, { spinning: syncing }),
13843
+ syncing ? t2("syncing") : t2("syncNow")
13844
+ ] }),
13845
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hint", children: snap.sync.last ? t2("lastSync", { time: relativeTime(L, snap.sync.last.finishedAt, now) }) : snap.upload.lastUploadAt ? t2("lastUpload", { time: relativeTime(L, snap.upload.lastUploadAt, now) }) : t2("neverUploaded") })
13846
+ ] })
13740
13847
  ] })
13741
13848
  ] }),
13742
13849
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("footer", { className: "footer", children: [
@@ -573,3 +573,102 @@ button.ghost:hover {
573
573
  color: var(--muted);
574
574
  user-select: text;
575
575
  }
576
+
577
+ /* --- full sync --- */
578
+ button.icon-btn {
579
+ display: inline-flex;
580
+ align-items: center;
581
+ justify-content: center;
582
+ padding: 3px;
583
+ border-radius: 6px;
584
+ color: var(--muted);
585
+ }
586
+ button.icon-btn:hover:not(:disabled) {
587
+ color: var(--text);
588
+ }
589
+ button:disabled {
590
+ cursor: default;
591
+ opacity: 0.6;
592
+ }
593
+ .sync-icon {
594
+ width: 13px;
595
+ height: 13px;
596
+ flex: none;
597
+ }
598
+ .sync-icon.spinning {
599
+ animation: sync-spin 0.9s linear infinite;
600
+ color: var(--accent);
601
+ }
602
+ @keyframes sync-spin {
603
+ to {
604
+ transform: rotate(360deg);
605
+ }
606
+ }
607
+ @media (prefers-reduced-motion: reduce) {
608
+ .sync-icon.spinning {
609
+ animation-duration: 2.4s;
610
+ }
611
+ }
612
+ .sync-bar {
613
+ display: flex;
614
+ align-items: center;
615
+ gap: 8px;
616
+ margin: 10px 12px 0;
617
+ padding: 8px 10px;
618
+ border: 1px solid var(--border);
619
+ border-radius: 8px;
620
+ background: var(--accent-soft);
621
+ font-size: 11px;
622
+ color: var(--text);
623
+ }
624
+ .sync-bar .msg {
625
+ flex: 1;
626
+ min-width: 0;
627
+ overflow: hidden;
628
+ text-overflow: ellipsis;
629
+ white-space: nowrap;
630
+ }
631
+ .sync-bar.done { background: rgba(12, 163, 12, 0.1); }
632
+ .sync-bar.failed { background: rgba(208, 59, 59, 0.1); color: var(--critical); }
633
+ /* the result lists every agent it found — let it wrap onto a second line rather than truncate */
634
+ .sync-bar.done .msg,
635
+ .sync-bar.failed .msg {
636
+ white-space: normal;
637
+ overflow: visible;
638
+ }
639
+ .sync-row {
640
+ margin-top: 8px;
641
+ padding-top: 8px;
642
+ border-top: 1px solid var(--border);
643
+ }
644
+ button.sync-btn {
645
+ display: inline-flex;
646
+ align-items: center;
647
+ gap: 5px;
648
+ padding: 3px 8px;
649
+ border-color: var(--border);
650
+ }
651
+ .sync-row .hint {
652
+ flex: 1;
653
+ min-width: 0;
654
+ text-align: right;
655
+ font-size: 10.5px;
656
+ color: var(--muted);
657
+ overflow: hidden;
658
+ text-overflow: ellipsis;
659
+ white-space: nowrap;
660
+ }
661
+
662
+ /* --- dev build badge --- */
663
+ .badge-dev {
664
+ flex: none;
665
+ margin-left: 8px;
666
+ padding: 1px 6px;
667
+ border-radius: 5px;
668
+ font-size: 9.5px;
669
+ font-weight: 700;
670
+ letter-spacing: 0.06em;
671
+ text-transform: uppercase;
672
+ color: #fff;
673
+ background: var(--serious);
674
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-token-tracker",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Menu bar / system tray app and headless agent that tracks OpenAI Codex token usage, cache hits, model mix and API-equivalent cost — and syncs it to your team dashboard.",
5
5
  "license": "MIT",
6
6
  "author": "Chen Li",
@@ -47,31 +47,27 @@
47
47
  "publishConfig": {
48
48
  "access": "public"
49
49
  },
50
- "scripts": {
51
- "build": "node scripts/build.mjs",
52
- "dev": "node scripts/build.mjs --watch",
53
- "typecheck": "tsc --noEmit -p tsconfig.json",
54
- "start": "node bin/codex-tracker.js",
55
- "prepublishOnly": "pnpm build && pnpm typecheck",
56
- "build:icons": "node scripts/gen-icons.mjs",
57
- "test": "node scripts/test.mjs"
58
- },
59
- "dependencies": {
60
- "menubar": "^9.5.3"
61
- },
62
- "optionalDependencies": {
63
- "electron": "^38.8.6"
64
- },
65
50
  "devDependencies": {
66
- "@codex-tracker/backend": "workspace:*",
67
- "@codex-tracker/shared": "workspace:*",
68
51
  "@types/node": "^22.15.0",
69
52
  "@types/react": "^19.2.0",
70
53
  "@types/react-dom": "^19.2.0",
71
54
  "convex": "^1.45.0",
55
+ "electron": "^38.8.6",
72
56
  "esbuild": "^0.28.2",
57
+ "menubar": "^9.5.3",
73
58
  "react": "19.2.8",
74
59
  "react-dom": "19.2.8",
75
- "typescript": "~5.9.3"
60
+ "typescript": "~5.9.3",
61
+ "@codex-tracker/backend": "0.1.0",
62
+ "@codex-tracker/shared": "0.1.0"
63
+ },
64
+ "scripts": {
65
+ "build": "node scripts/build.mjs",
66
+ "build:release": "node scripts/build.mjs --release",
67
+ "dev": "node scripts/build.mjs --watch",
68
+ "typecheck": "tsc --noEmit -p tsconfig.json",
69
+ "start": "node bin/codex-tracker.js",
70
+ "build:icons": "node scripts/gen-icons.mjs",
71
+ "test": "node scripts/test.mjs"
76
72
  }
77
- }
73
+ }