codex-token-tracker 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/preload.js CHANGED
@@ -16,6 +16,9 @@ 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"),
20
+ checkUpdate: () => import_electron.ipcRenderer.invoke("update:check"),
21
+ installUpdate: () => import_electron.ipcRenderer.invoke("update:install"),
19
22
  quit: () => import_electron.ipcRenderer.invoke("quit")
20
23
  };
21
24
  import_electron.contextBridge.exposeInMainWorld("codexTracker", bridge);
@@ -12758,6 +12758,70 @@
12758
12758
  // src/renderer/App.tsx
12759
12759
  var import_react2 = __toESM(require_react());
12760
12760
 
12761
+ // ../shared/src/pricing.ts
12762
+ var LONG_CONTEXT_THRESHOLD = 272e3;
12763
+ var long = (threshold, input, cachedInput, output) => ({
12764
+ threshold,
12765
+ input,
12766
+ cachedInput,
12767
+ output
12768
+ });
12769
+ var DEFAULT_PRICING = {
12770
+ // GPT-5.6 family
12771
+ "gpt-5.6-sol": { input: 4, cachedInput: 0.4, output: 20, long: long(LONG_CONTEXT_THRESHOLD, 8, 0.8, 30) },
12772
+ "gpt-5.6-sol-codex": { input: 4, cachedInput: 0.4, output: 20, long: long(LONG_CONTEXT_THRESHOLD, 8, 0.8, 30) },
12773
+ "gpt-5.6-terra": { input: 2, cachedInput: 0.2, output: 12, long: long(LONG_CONTEXT_THRESHOLD, 4, 0.4, 18) },
12774
+ "gpt-5.6-terra-codex": { input: 2, cachedInput: 0.2, output: 12, long: long(LONG_CONTEXT_THRESHOLD, 4, 0.4, 18) },
12775
+ "gpt-5.6-luna": { input: 0.2, cachedInput: 0.02, output: 1.2, long: long(LONG_CONTEXT_THRESHOLD, 0.4, 0.04, 1.8) },
12776
+ "gpt-5.6-cyber": { input: 12.5, cachedInput: 1.25, output: 75 },
12777
+ // GPT-5.5 family
12778
+ "gpt-5.5": { input: 5, cachedInput: 0.5, output: 30, long: long(LONG_CONTEXT_THRESHOLD, 10, 1, 45) },
12779
+ "gpt-5.5-codex": { input: 5, cachedInput: 0.5, output: 30, long: long(LONG_CONTEXT_THRESHOLD, 10, 1, 45) },
12780
+ "gpt-5.5-pro": { input: 30, cachedInput: 30, output: 180, long: long(LONG_CONTEXT_THRESHOLD, 60, 60, 270) },
12781
+ "gpt-5.5-cyber": { input: 12.5, cachedInput: 1.25, output: 75 },
12782
+ // GPT-5.4 family
12783
+ "gpt-5.4": { input: 2.5, cachedInput: 0.25, output: 15, long: long(LONG_CONTEXT_THRESHOLD, 5, 0.5, 22.5) },
12784
+ "gpt-5.4-codex": { input: 2.5, cachedInput: 0.25, output: 15, long: long(LONG_CONTEXT_THRESHOLD, 5, 0.5, 22.5) },
12785
+ "gpt-5.4-mini": { input: 0.75, cachedInput: 0.075, output: 4.5 },
12786
+ "gpt-5.4-nano": { input: 0.2, cachedInput: 0.02, output: 1.25 },
12787
+ "gpt-5.4-pro": { input: 30, cachedInput: 30, output: 180, long: long(LONG_CONTEXT_THRESHOLD, 60, 60, 270) },
12788
+ // GPT-5.3 family (Codex-only release)
12789
+ "gpt-5.3": { input: 1.75, cachedInput: 0.175, output: 14 },
12790
+ "gpt-5.3-codex": { input: 1.75, cachedInput: 0.175, output: 14 },
12791
+ // GPT-5.2 family
12792
+ "gpt-5.2": { input: 1.75, cachedInput: 0.175, output: 14 },
12793
+ "gpt-5.2-codex": { input: 1.75, cachedInput: 0.175, output: 14 },
12794
+ "gpt-5.2-pro": { input: 21, cachedInput: 21, output: 168 },
12795
+ // GPT-5.1 family
12796
+ "gpt-5.1": { input: 1.25, cachedInput: 0.125, output: 10 },
12797
+ "gpt-5.1-codex": { input: 1.25, cachedInput: 0.125, output: 10 },
12798
+ "gpt-5.1-codex-max": { input: 1.25, cachedInput: 0.125, output: 10 },
12799
+ "gpt-5.1-codex-mini": { input: 0.25, cachedInput: 0.025, output: 2 },
12800
+ // GPT-5 family
12801
+ "gpt-5": { input: 1.25, cachedInput: 0.125, output: 10 },
12802
+ "gpt-5-codex": { input: 1.25, cachedInput: 0.125, output: 10 },
12803
+ "gpt-5-mini": { input: 0.25, cachedInput: 0.025, output: 2 },
12804
+ "gpt-5-nano": { input: 0.05, cachedInput: 5e-3, output: 0.4 },
12805
+ "gpt-5-pro": { input: 15, cachedInput: 15, output: 120 },
12806
+ "gpt-5-search-api": { input: 1.25, cachedInput: 0.125, output: 10 },
12807
+ // ChatGPT-tuned endpoint
12808
+ "chat-latest": { input: 5, cachedInput: 0.5, output: 30 },
12809
+ // GPT-4.1 / 4o family
12810
+ "gpt-4.1": { input: 2, cachedInput: 0.5, output: 8 },
12811
+ "gpt-4.1-mini": { input: 0.4, cachedInput: 0.1, output: 1.6 },
12812
+ "gpt-4.1-nano": { input: 0.1, cachedInput: 0.025, output: 0.4 },
12813
+ "gpt-4o": { input: 2.5, cachedInput: 1.25, output: 10 },
12814
+ "gpt-4o-mini": { input: 0.15, cachedInput: 0.075, output: 0.6 },
12815
+ // Reasoning models
12816
+ o1: { input: 15, cachedInput: 7.5, output: 60 },
12817
+ "o1-pro": { input: 150, cachedInput: 150, output: 600 },
12818
+ o3: { input: 2, cachedInput: 0.5, output: 8 },
12819
+ "o3-pro": { input: 20, cachedInput: 20, output: 80 },
12820
+ "o3-mini": { input: 1.1, cachedInput: 0.55, output: 4.4 },
12821
+ "o4-mini": { input: 1.1, cachedInput: 0.275, output: 4.4 },
12822
+ "codex-mini-latest": { input: 1.5, cachedInput: 0.375, output: 6 }
12823
+ };
12824
+
12761
12825
  // ../shared/src/time.ts
12762
12826
  var SECOND = 1e3;
12763
12827
  var MINUTE = 60 * SECOND;
@@ -12909,6 +12973,7 @@
12909
12973
  estimatedPricingHint: "Pricing estimated: model not in the price table",
12910
12974
  other: "Other",
12911
12975
  lastUpload: "Last upload {time}",
12976
+ lastSync: "Last full sync {time}",
12912
12977
  neverUploaded: "Not uploaded yet",
12913
12978
  uploadError: "Upload error: {message}",
12914
12979
  allDevicesToday: "All devices today",
@@ -12927,16 +12992,44 @@
12927
12992
  launchAtLogin: "Launch at login",
12928
12993
  refresh: "Refresh",
12929
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}",
12930
13007
  sessions: "{n} sessions",
12931
13008
  files: "{n} files",
12932
13009
  noData: "No usage found yet. Run Codex and this will fill in.",
12933
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}",
12934
13015
  justNow: "just now",
12935
13016
  never: "never",
12936
13017
  local: "Local",
12937
13018
  remote: "Other devices",
13019
+ // updates
13020
+ update: "Update",
13021
+ updateAvailable: "Update to v{version}",
13022
+ updateNewVersion: "Version {version} is available (you have v{current})",
13023
+ updateUpToDate: "Up to date",
13024
+ updateChecking: "Checking\u2026",
13025
+ updateInstalling: "Installing\u2026",
13026
+ updateInstalled: "Updated to v{version} \u2014 restart to apply",
13027
+ updateFailed: "Update failed \u2014 run this yourself:",
13028
+ updateCheckFailed: "Update check failed: {message}",
13029
+ checkForUpdates: "Check for updates",
13030
+ releaseNotes: "Release notes",
12938
13031
  // CLI
12939
- cliUsage: `Usage: codex-tracker [command] [options]
13032
+ cliUsage: `Usage: codex-token-tracker [command] [options] (alias: codex-tracker)
12940
13033
 
12941
13034
  Commands:
12942
13035
  (none) Start the menu bar app (falls back to agent mode without a display)
@@ -12946,11 +13039,14 @@ Commands:
12946
13039
  login Connect this device to your dashboard (--dashboard <url>)
12947
13040
  logout Disconnect this device
12948
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)
12949
13043
  paths Show detected session directories (Codex, pi, OpenCode, Cline/Roo/Kilo, Hermes, custom)
13044
+ update Install the newest published version (--check only reports)
12950
13045
  config get|set Read or change settings (config set uploadIntervalSec 30, config set sources.pi false)
12951
13046
  lang <en|zh|auto> Set the display language
12952
13047
 
12953
13048
  Options:
13049
+ --check update: report the newest version without installing it
12954
13050
  --dashboard <url> Dashboard URL (self-hosted)
12955
13051
  --background Start the menu bar app detached and return
12956
13052
  --version, -v Print version
@@ -12992,7 +13088,21 @@ Options:
12992
13088
  cliConfigSet: "Set {key} = {value}",
12993
13089
  cliConfigUnknownKey: "Unknown config key: {key}. Keys: {keys}",
12994
13090
  cliLangSet: "Language set to {lang}",
12995
- cliConfigDir: "Config: {dir}"
13091
+ cliConfigDir: "Config: {dir}",
13092
+ cliUpdateLatest: "codex-token-tracker {version} is the latest version.",
13093
+ cliUpdateAvailable: "Update available: {current} \u2192 {latest}",
13094
+ cliUpdateRunning: "Running: {command}",
13095
+ cliUpdateDone: "Updated to {version}. Restart the menu bar app (or rerun the CLI) to use it.",
13096
+ cliUpdateFailed: "Update failed (exit {code}). Run it yourself: {command}",
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."
12996
13106
  };
12997
13107
 
12998
13108
  // src/i18n/zh.ts
@@ -13040,6 +13150,7 @@ Options:
13040
13150
  estimatedPricingHint: "\u4EF7\u683C\u4E3A\u4F30\u7B97\u503C\uFF1A\u8BE5\u6A21\u578B\u4E0D\u5728\u4EF7\u76EE\u8868\u4E2D",
13041
13151
  other: "\u5176\u4ED6",
13042
13152
  lastUpload: "\u4E0A\u6B21\u4E0A\u4F20 {time}",
13153
+ lastSync: "\u4E0A\u6B21\u5B8C\u6574\u540C\u6B65 {time}",
13043
13154
  neverUploaded: "\u5C1A\u672A\u4E0A\u4F20",
13044
13155
  uploadError: "\u4E0A\u4F20\u5931\u8D25\uFF1A{message}",
13045
13156
  allDevicesToday: "\u6240\u6709\u8BBE\u5907\u4ECA\u65E5",
@@ -13058,15 +13169,42 @@ Options:
13058
13169
  launchAtLogin: "\u5F00\u673A\u81EA\u542F\u52A8",
13059
13170
  refresh: "\u5237\u65B0",
13060
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}",
13061
13184
  sessions: "{n} \u4E2A\u4F1A\u8BDD",
13062
13185
  files: "{n} \u4E2A\u6587\u4EF6",
13063
13186
  noData: "\u8FD8\u6CA1\u6709\u7528\u91CF\u6570\u636E\u3002\u8FD0\u884C Codex \u540E\u8FD9\u91CC\u4F1A\u81EA\u52A8\u66F4\u65B0\u3002",
13064
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}",
13065
13192
  justNow: "\u521A\u521A",
13066
13193
  never: "\u4ECE\u672A",
13067
13194
  local: "\u672C\u673A",
13068
13195
  remote: "\u5176\u4ED6\u8BBE\u5907",
13069
- cliUsage: `\u7528\u6CD5\uFF1Acodex-tracker [\u547D\u4EE4] [\u9009\u9879]
13196
+ update: "\u66F4\u65B0",
13197
+ updateAvailable: "\u66F4\u65B0\u5230 v{version}",
13198
+ updateNewVersion: "\u6709\u65B0\u7248\u672C {version}\uFF08\u5F53\u524D v{current}\uFF09",
13199
+ updateUpToDate: "\u5DF2\u662F\u6700\u65B0\u7248\u672C",
13200
+ updateChecking: "\u68C0\u67E5\u4E2D\u2026",
13201
+ updateInstalling: "\u5B89\u88C5\u4E2D\u2026",
13202
+ updateInstalled: "\u5DF2\u66F4\u65B0\u5230 v{version} \u2014 \u91CD\u542F\u540E\u751F\u6548",
13203
+ updateFailed: "\u66F4\u65B0\u5931\u8D25 \u2014 \u8BF7\u624B\u52A8\u6267\u884C\uFF1A",
13204
+ updateCheckFailed: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF1A{message}",
13205
+ checkForUpdates: "\u68C0\u67E5\u66F4\u65B0",
13206
+ releaseNotes: "\u66F4\u65B0\u65E5\u5FD7",
13207
+ cliUsage: `\u7528\u6CD5\uFF1Acodex-token-tracker [\u547D\u4EE4] [\u9009\u9879] \uFF08\u522B\u540D\uFF1Acodex-tracker\uFF09
13070
13208
 
13071
13209
  \u547D\u4EE4\uFF1A
13072
13210
  (\u65E0) \u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\uFF08\u65E0\u663E\u793A\u73AF\u5883\u65F6\u81EA\u52A8\u5207\u6362\u4E3A agent \u6A21\u5F0F\uFF09
@@ -13076,11 +13214,14 @@ Options:
13076
13214
  login \u5C06\u672C\u8BBE\u5907\u8FDE\u63A5\u5230\u4EEA\u8868\u76D8\uFF08--dashboard <url>\uFF09
13077
13215
  logout \u65AD\u5F00\u672C\u8BBE\u5907
13078
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
13079
13218
  paths \u663E\u793A\u68C0\u6D4B\u5230\u7684\u4F1A\u8BDD\u76EE\u5F55\uFF08Codex\u3001pi\u3001OpenCode\u3001Cline/Roo/Kilo\u3001Hermes\u3001\u81EA\u5B9A\u4E49\uFF09
13219
+ update \u5B89\u88C5\u6700\u65B0\u53D1\u5E03\u7248\u672C\uFF08--check \u53EA\u68C0\u67E5\u4E0D\u5B89\u88C5\uFF09
13080
13220
  config get|set \u8BFB\u53D6\u6216\u4FEE\u6539\u8BBE\u7F6E\uFF08config set uploadIntervalSec 30\uFF0Cconfig set sources.pi false\uFF09
13081
13221
  lang <en|zh|auto> \u8BBE\u7F6E\u663E\u793A\u8BED\u8A00
13082
13222
 
13083
13223
  \u9009\u9879\uFF1A
13224
+ --check update\uFF1A\u53EA\u62A5\u544A\u6700\u65B0\u7248\u672C\uFF0C\u4E0D\u6267\u884C\u5B89\u88C5
13084
13225
  --dashboard <url> \u4EEA\u8868\u76D8\u5730\u5740\uFF08\u81EA\u6258\u7BA1\uFF09
13085
13226
  --background \u4EE5\u540E\u53F0\u65B9\u5F0F\u542F\u52A8\u83DC\u5355\u680F\u5E94\u7528\u5E76\u7ACB\u5373\u8FD4\u56DE
13086
13227
  --version, -v \u663E\u793A\u7248\u672C
@@ -13122,7 +13263,21 @@ Options:
13122
13263
  cliConfigSet: "\u5DF2\u8BBE\u7F6E {key} = {value}",
13123
13264
  cliConfigUnknownKey: "\u672A\u77E5\u914D\u7F6E\u9879\uFF1A{key}\u3002\u53EF\u7528\uFF1A{keys}",
13124
13265
  cliLangSet: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A {lang}",
13125
- cliConfigDir: "\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}"
13266
+ cliConfigDir: "\u914D\u7F6E\u76EE\u5F55\uFF1A{dir}",
13267
+ cliUpdateLatest: "codex-token-tracker {version} \u5DF2\u662F\u6700\u65B0\u7248\u672C\u3002",
13268
+ cliUpdateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C\uFF1A{current} \u2192 {latest}",
13269
+ cliUpdateRunning: "\u6B63\u5728\u6267\u884C\uFF1A{command}",
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",
13271
+ cliUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF1A{command}",
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"
13126
13281
  };
13127
13282
 
13128
13283
  // src/i18n/index.ts
@@ -13367,6 +13522,59 @@ Options:
13367
13522
  function levelClass(pct) {
13368
13523
  return pct >= 85 ? "critical" : pct >= 60 ? "warning" : "";
13369
13524
  }
13525
+ function UpdateBar({ update, t: t2 }) {
13526
+ if (update.status === "installed") {
13527
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "update-bar done", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "msg", children: t2("updateInstalled", { version: update.latest ?? "?" }) }) });
13528
+ }
13529
+ if (update.status === "failed") {
13530
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "update-bar failed", children: [
13531
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "msg", title: update.log ?? "", children: t2("updateFailed") }),
13532
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "cmd", children: update.command })
13533
+ ] });
13534
+ }
13535
+ if (update.status === "installing") {
13536
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "update-bar", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "msg", children: t2("updateInstalling") }) });
13537
+ }
13538
+ if (!update.available) return null;
13539
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "update-bar", children: [
13540
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "msg", children: t2("updateNewVersion", { version: update.latest ?? "?", current: update.current }) }),
13541
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "primary", onClick: () => void bridge().installUpdate(), children: t2("update") })
13542
+ ] });
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
+ }
13370
13578
  function App() {
13371
13579
  const [snap, setSnap] = (0, import_react2.useState)(null);
13372
13580
  const [, setClock] = (0, import_react2.useState)(0);
@@ -13397,24 +13605,39 @@ Options:
13397
13605
  const agents = agentsPeriod === "today" ? snap.byAgentToday : snap.byAgentMonth;
13398
13606
  const auth = snap.auth;
13399
13607
  const rl = snap.rateLimits;
13608
+ const syncing = snap.sync.status === "running";
13400
13609
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app", children: [
13401
13610
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "header", children: [
13402
13611
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "brand", children: [
13403
13612
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Logo, {}),
13404
13613
  t2("appName")
13405
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,
13406
13616
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "spacer" }),
13407
13617
  live ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "pill live", title: live.projectName ?? "", children: [
13408
13618
  live.tokensPerSecond.toFixed(1),
13409
13619
  " ",
13410
13620
  t2("tokensPerSec")
13411
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
+ ),
13412
13633
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "seg", role: "group", "aria-label": t2("language"), children: [
13413
13634
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: L === "en" ? "active" : "", onClick: () => void bridge().setLanguage("en"), children: "EN" }),
13414
13635
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: L === "zh" ? "active" : "", onClick: () => void bridge().setLanguage("zh"), children: "\u4E2D\u6587" })
13415
13636
  ] })
13416
13637
  ] }),
13417
13638
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "scroll", children: [
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 }),
13418
13641
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "card", children: [
13419
13642
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "card-title", children: [
13420
13643
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t2("today") }),
@@ -13613,7 +13836,14 @@ Options:
13613
13836
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "empty", style: { flex: 1 }, children: t2("signedOut") }),
13614
13837
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "primary", onClick: () => void bridge().login(), children: t2("signIn") })
13615
13838
  ] }),
13616
- 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
+ ] })
13617
13847
  ] })
13618
13848
  ] }),
13619
13849
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("footer", { className: "footer", children: [
@@ -543,3 +543,132 @@ button.ghost:hover {
543
543
  .source-line .dot { width: 6px; height: 6px; border-radius: 50%; background: #0ca30c; flex: none; }
544
544
  .source-line.stale .dot { background: #fab219; }
545
545
  .source-line .err-hint { color: #d03b3b; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
546
+
547
+ /* --- update banner --- */
548
+ .update-bar {
549
+ display: flex;
550
+ align-items: center;
551
+ gap: 8px;
552
+ margin: 10px 12px 0;
553
+ padding: 8px 10px;
554
+ border: 1px solid var(--border);
555
+ border-radius: 8px;
556
+ background: rgba(99, 102, 241, 0.09);
557
+ font-size: 11px;
558
+ color: var(--text);
559
+ }
560
+ .update-bar .msg {
561
+ flex: 1;
562
+ min-width: 0;
563
+ overflow: hidden;
564
+ text-overflow: ellipsis;
565
+ white-space: nowrap;
566
+ }
567
+ .update-bar.failed { background: rgba(208, 59, 59, 0.1); }
568
+ .update-bar.done { background: rgba(12, 163, 12, 0.1); }
569
+ .update-bar button { flex: none; }
570
+ .update-bar .cmd {
571
+ font-family: var(--mono);
572
+ font-size: 10px;
573
+ color: var(--muted);
574
+ user-select: text;
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.1.1",
3
+ "version": "0.2.1",
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",
@@ -25,6 +25,7 @@
25
25
  "convex"
26
26
  ],
27
27
  "bin": {
28
+ "codex-token-tracker": "bin/codex-tracker.js",
28
29
  "codex-tracker": "bin/codex-tracker.js"
29
30
  },
30
31
  "main": "dist/main.js",
@@ -32,7 +33,8 @@
32
33
  "bin",
33
34
  "dist",
34
35
  "assets",
35
- "README.md"
36
+ "README.md",
37
+ "CHANGELOG.md"
36
38
  ],
37
39
  "engines": {
38
40
  "node": ">=20"
@@ -65,6 +67,7 @@
65
67
  },
66
68
  "scripts": {
67
69
  "build": "node scripts/build.mjs",
70
+ "build:release": "node scripts/build.mjs --release",
68
71
  "dev": "node scripts/build.mjs --watch",
69
72
  "typecheck": "tsc --noEmit -p tsconfig.json",
70
73
  "start": "node bin/codex-tracker.js",