easy-codex 0.2.0 → 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/README.md CHANGED
@@ -19,7 +19,7 @@ easy-codex
19
19
  显示透明圆环(macOS):
20
20
 
21
21
  ```sh
22
- easy-codex overlay
22
+ easy-codex show
23
23
  ```
24
24
 
25
25
  需要 Node.js 22+ 和已登录的官方 Codex CLI。安装包自带浮窗,使用者无需下载源码、安装依赖或编译。
@@ -48,7 +48,7 @@ npm test
48
48
  macOS 14+ 安装后运行:
49
49
 
50
50
  ```sh
51
- easy-codex overlay
51
+ easy-codex show
52
52
  ```
53
53
 
54
54
  独立 AppKit 浮窗在 Codex 位于前台时显示,切到其他应用、最小化或关闭 Codex 窗口后隐藏。默认位于 Codex 窗口左下角头像上方;拖动可校准位置,偏移量保存在本机。它按窗口边界跟随,不自动识别头像;侧栏布局变化后可再次拖动。首版跟随最前面的一个 Codex 普通窗口。
@@ -81,3 +81,9 @@ GitHub Actions 在 `v*` 标签上测试、构建通用 macOS 浮窗,并发布
81
81
  ## 电子时钟
82
82
 
83
83
  透明时钟位于圆环上方,竖排居中,默认间距10px,可独立拖动。默认硅谷,城市顺序为硅谷、洛杉矶、西雅图、华盛顿特区、哥伦布(俄亥俄州)、新加坡、上海,也可自定义 IANA 时区;设置自动保存。仅显示时分,按目标当地时间00–09红色、09–15绿色、15–24橙色,自动处理夏令时,不修改电脑系统时区。菜单“恢复默认位置”应用竖排默认布局;已有拖动位置继续保留。
84
+
85
+
86
+
87
+ 浮窗使用 `easy-codex show` 启动;原 `overlay` 命令保留兼容。
88
+
89
+ 周倒计时只显示最大单位,例如“6天后 25日 06:17 重置”,不足一天按小时、不足一小时按分钟。5小时窗口保留时分组合,例如“3小时2分后 07:59 重置”,没有小时则“22分后 07:59 重置”,省略为零的单位。绝对重置时刻使用电脑本地时区;周含日,5小时不含日期。
package/bin/easy-codex.js CHANGED
@@ -5,16 +5,16 @@ import { readUsage, formatUsage } from '../src/usage.js';
5
5
 
6
6
  const args = process.argv.slice(2);
7
7
  if (args.length === 1 && ['--help', '-h'].includes(args[0])) {
8
- console.log('用法:easy-codex [usage|overlay]\n显示已登录 Codex 账号的剩余百分比和重置时间。需要官方 Codex CLI。');
8
+ console.log('用法:easy-codex [usage|show|overlay]\n显示已登录 Codex 账号的剩余百分比和重置时间。需要官方 Codex CLI。');
9
9
  } else if (args.length === 1 && ['--version', '-v'].includes(args[0])) {
10
10
  console.log(version);
11
- } else if (args.length === 1 && args[0] === 'overlay') {
11
+ } else if (args.length === 1 && ['show', 'overlay'].includes(args[0])) {
12
12
  try { const { launchOverlay } = await import('../src/overlay.js'); await launchOverlay(); }
13
13
  catch (error) { console.error(`easy-codex: ${error.message}`); process.exitCode = 1; }
14
14
  } else if (args.length === 0 || (args.length === 1 && args[0] === 'usage')) {
15
15
  try { console.log(formatUsage(await readUsage())); }
16
16
  catch (error) { console.error(`easy-codex: ${error.message}`); process.exitCode = 1; }
17
17
  } else {
18
- console.error('未知参数。用法:easy-codex [usage|overlay],或 easy-codex --help');
18
+ console.error('未知参数。用法:easy-codex [usage|show|overlay],或 easy-codex --help');
19
19
  process.exitCode = 1;
20
20
  }
@@ -161,6 +161,29 @@ struct ClockCity {
161
161
  ]
162
162
  }
163
163
 
164
+ func resetDescription(_ resetsAt: Double?, weekly: Bool, now: Date = Date(), zone: TimeZone = .current) -> String {
165
+ guard let resetsAt, resetsAt.isFinite, abs(resetsAt) < 8.64e12 else { return "重置时间未知" }
166
+ let remaining = resetsAt - now.timeIntervalSince1970
167
+ guard remaining > 0 else { return "已到重置时间,等待更新" }
168
+ let formatter = DateFormatter()
169
+ formatter.locale = Locale(identifier: "zh_CN")
170
+ formatter.timeZone = zone
171
+ formatter.dateFormat = weekly ? "d'日' HH:mm" : "HH:mm"
172
+ let time = formatter.string(from: Date(timeIntervalSince1970: resetsAt))
173
+ let duration: String
174
+ if weekly {
175
+ if remaining >= 86400 { duration = "\(Int(remaining / 86400))天" }
176
+ else if remaining >= 3600 { duration = "\(Int(remaining / 3600))小时" }
177
+ else { duration = "\(Int(ceil(remaining / 60)))分钟" }
178
+ } else {
179
+ let totalMinutes = Int(ceil(remaining / 60))
180
+ let hours = totalMinutes / 60
181
+ let minutes = totalMinutes % 60
182
+ duration = (hours > 0 ? "\(hours)小时" : "") + (minutes > 0 ? "\(minutes)分" : "")
183
+ }
184
+ return "\(duration)后 \(time) 重置"
185
+ }
186
+
164
187
  func clockText(at date: Date, zone: TimeZone) -> String {
165
188
  let formatter = DateFormatter()
166
189
  formatter.locale = Locale(identifier: "en_US_POSIX")
@@ -372,15 +395,8 @@ final class Overlay: NSObject, NSApplicationDelegate, NSMenuDelegate {
372
395
  let id = minutes == 10080 ? "weekly" : "fiveHour"
373
396
  guard let window = codexWindow(snapshot?.windows ?? [], minutes: minutes) else { continue }
374
397
  let remaining = window.remaining.map { "\(Int($0.rounded()))%" } ?? "未知"
375
- let reset: String
376
- if let seconds = window.resetsAt {
377
- let formatter = DateFormatter()
378
- formatter.locale = Locale(identifier: "zh_CN")
379
- formatter.timeZone = .current
380
- formatter.dateFormat = "MM-dd HH:mm"
381
- reset = formatter.string(from: Date(timeIntervalSince1970: seconds))
382
- } else { reset = "未知" }
383
- let option = item("\(label) · \(remaining) · 重置 \(reset)", #selector(selectPercentage(_:)))
398
+ let reset = resetDescription(window.resetsAt, weekly: minutes == 10080)
399
+ let option = item("\(label) · \(remaining) · \(reset)", #selector(selectPercentage(_:)))
384
400
  option.representedObject = id
385
401
  option.state = (ring.displaysWeeklyPercentage == (id == "weekly")) ? .on : .off
386
402
  menu.addItem(option)
@@ -388,7 +404,7 @@ final class Overlay: NSObject, NSApplicationDelegate, NSMenuDelegate {
388
404
  menu.addItem(item(query == nil ? "立即刷新" : "正在刷新…", query == nil ? #selector(refresh) : nil))
389
405
  menu.addItem(item("恢复默认位置", #selector(resetPosition)))
390
406
  menu.addItem(.separator())
391
- let toggle = item("显示电子时钟", #selector(toggleClock))
407
+ let toggle = item("显示时间", #selector(toggleClock))
392
408
  toggle.state = clockEnabled ? .on : .off
393
409
  menu.addItem(toggle)
394
410
  let zones = NSMenu()
@@ -399,7 +415,7 @@ final class Overlay: NSObject, NSApplicationDelegate, NSMenuDelegate {
399
415
  zones.addItem(option)
400
416
  }
401
417
  zones.addItem(item("自定义时区…", #selector(customTimeZone)))
402
- let zoneItem = item("时区 · \(clock.cityName)", nil)
418
+ let zoneItem = item("\(clock.cityName)·\(clockText(at: Date(), zone: clock.zone))", nil)
403
419
  zoneItem.submenu = zones
404
420
  menu.addItem(zoneItem)
405
421
  menu.addItem(.separator())
@@ -479,6 +495,19 @@ struct Main {
479
495
  let otherHours = UsageWindow(id: "other:primary", label: "5小时", name: "other", remaining: 10, resetsAt: nil, windowDurationMins: 300)
480
496
  precondition(codexWindow([otherHours, weekOnly], minutes: 10080)?.remaining == 80)
481
497
  precondition(codexWindow([otherHours, weekOnly], minutes: 300) == nil)
498
+ let countdownStart = ISO8601DateFormatter().date(from: "2026-10-15T04:57:00Z")!
499
+ let countdownUTC = TimeZone(secondsFromGMT: 0)!
500
+ precondition(resetDescription(countdownStart.timeIntervalSince1970 + 10920, weekly: false, now: countdownStart, zone: countdownUTC) == "3小时2分后 07:59 重置")
501
+ let weekStart = ISO8601DateFormatter().date(from: "2026-10-14T08:03:00Z")!
502
+ precondition(resetDescription(weekStart.timeIntervalSince1970 + 345360, weekly: true, now: weekStart, zone: countdownUTC) == "3天后 18日 07:59 重置")
503
+ precondition(resetDescription(nil, weekly: true) == "重置时间未知")
504
+ precondition(resetDescription(countdownStart.timeIntervalSince1970, weekly: false, now: countdownStart) == "已到重置时间,等待更新")
505
+ precondition(resetDescription(countdownStart.timeIntervalSince1970 + 1, weekly: false, now: countdownStart, zone: countdownUTC) == "1分后 04:57 重置")
506
+ precondition(resetDescription(countdownStart.timeIntervalSince1970 + 86160, weekly: true, now: countdownStart, zone: countdownUTC) == "23小时后 16日 04:53 重置")
507
+ precondition(resetDescription(countdownStart.timeIntervalSince1970 + 120, weekly: true, now: countdownStart, zone: countdownUTC) == "2分钟后 15日 04:59 重置")
508
+ precondition(resetDescription(countdownStart.timeIntervalSince1970 + 86400, weekly: true, now: countdownStart, zone: countdownUTC) == "1天后 16日 04:57 重置")
509
+ precondition(resetDescription(countdownStart.timeIntervalSince1970 + 86399, weekly: true, now: countdownStart, zone: countdownUTC) == "23小时后 16日 04:56 重置")
510
+ precondition(resetDescription(countdownStart.timeIntervalSince1970 + 3599, weekly: false, now: countdownStart, zone: countdownUTC) == "1小时后 05:56 重置")
482
511
  let testRing = RingView()
483
512
  testRing.showFiveHourRing = false
484
513
  precondition(testRing.displaysWeeklyPercentage)
@@ -5,7 +5,7 @@
5
5
  <key>CFBundleIdentifier</key><string>com.unionschool.easy-codex.overlay</string>
6
6
  <key>CFBundleName</key><string>Easy Codex Overlay</string>
7
7
  <key>CFBundlePackageType</key><string>APPL</string>
8
- <key>CFBundleShortVersionString</key><string>0.2.0</string>
8
+ <key>CFBundleShortVersionString</key><string>0.2.1</string>
9
9
  <key>LSMinimumSystemVersion</key><string>14.0</string>
10
10
  <key>LSUIElement</key><true/>
11
11
  <key>NSHighResolutionCapable</key><true/>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-codex",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Codex usage limits and transparent world clock overlay",
5
5
  "type": "module",
6
6
  "bin": {