easy-codex 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +37 -1
- package/bin/easy-codex.js +5 -2
- package/native/Overlay.swift +20 -3
- package/native/build/EasyCodexOverlay.app/Contents/Info.plist +1 -1
- package/native/build/EasyCodexOverlay.app/Contents/MacOS/EasyCodexOverlay +0 -0
- package/package.json +3 -2
- package/src/overlay.js +6 -4
- package/src/update.js +45 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 更新记录
|
|
2
|
+
|
|
3
|
+
## 0.2.2 — 2026-10-19
|
|
4
|
+
|
|
5
|
+
### 新增
|
|
6
|
+
|
|
7
|
+
- 新增 `easy-codex update`,复用npm升级当前安装目录中的正式包。
|
|
8
|
+
- `show` 自动退出自身旧浮窗并打开新版,保留现有设置;无法退出旧实例时明确失败,避免重复窗口。
|
|
9
|
+
|
|
10
|
+
### 调整
|
|
11
|
+
|
|
12
|
+
- 两个工具统一所选城市的时间颜色:00:00–09:00红、09:00–18:00绿、18:00–24:00橙。
|
|
13
|
+
- 将绿色结束/橙色开始从15:00调整至18:00;午夜与09:00切换点不变,仍自动处理夏令时。
|
|
14
|
+
- 保留升级前的时区、城市、位置和显示选择,额度圆环颜色规则保持原样。
|
|
15
|
+
- README与版本说明随包交付,保留show快捷启动和overlay兼容。
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## 0.2.1 — 2026-10-19
|
|
19
|
+
|
|
20
|
+
### 新增与调整
|
|
21
|
+
|
|
22
|
+
- 新增 `easy-codex show` 快捷启动,保留 `overlay` 兼容。
|
|
23
|
+
- 菜单增加重置倒计时:周只显示最大单位,满天按天、不足一天按小时、不足一小时按分钟;5小时窗口保留时分组合,无小时则仅分钟。
|
|
24
|
+
- 时间开关改为“显示时间”;城市入口显示“洛杉矶·05:23”,使用所选城市当前时间且点号无空格。
|
|
25
|
+
- 兼容已有设置,升级后保留城市、时区、位置与显示选择。
|
|
26
|
+
|
|
27
|
+
### 使用示例
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm i -g easy-codex@latest
|
|
31
|
+
easy-codex show
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
升级前退出旧 EC 浮窗。菜单重置时刻按电脑本地时区;时间浮窗使用所选城市时区。已有时间颜色规则保持:当地00:00–09:00红、09:00–15:00绿、15:00–24:00橙,自动处理夏令时。
|
|
35
|
+
|
|
36
|
+
尚未发布的 Agent 协作开发入口不属于此版本。
|
package/README.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
显示当前本机 Codex 登录账号的剩余额度百分比和重置时间。第三方辅助工具,与 OpenAI 无官方隶属关系。
|
|
4
4
|
|
|
5
|
+
## v0.2.2 新增与调整
|
|
6
|
+
|
|
7
|
+
- **命令更新**:新增 `easy-codex update`,从官方 npm 更新当前全局安装。
|
|
8
|
+
- **自动重开**:`easy-codex show` 会先退出自身旧浮窗,再打开新版;设置保持不变。
|
|
9
|
+
|
|
10
|
+
- 统一时间颜色:所选城市当地时间 **00:00–09:00 红色、09:00–18:00 绿色、18:00–24:00 橙色**,绿色时段延长到18:00。
|
|
11
|
+
- 09:00转绿、18:00转橙、午夜转红,自动适应目标时区夏令时。
|
|
12
|
+
- 保留已有时区、城市、位置和显示选择;额度圆环颜色阈值保持原有规则。
|
|
13
|
+
|
|
14
|
+
完整版本说明见 [CHANGELOG](CHANGELOG.md)。
|
|
15
|
+
|
|
16
|
+
## v0.2.1 新增功能
|
|
17
|
+
|
|
18
|
+
- **快捷启动**:新增 `easy-codex show`,原 `overlay` 命令继续可用。
|
|
19
|
+
- **重置倒计时**:周额度显示“5天后”等最大单位;5小时窗口显示“3小时2分后”,不足一小时只显示分钟。同时保留电脑本地时区的重置时刻。
|
|
20
|
+
- **时间菜单优化**:开关改为“显示时间”;城市入口直接显示当前当地时间,如 `洛杉矶·05:23`,点号无空格。
|
|
21
|
+
- **升级兼容**:保留已保存的时区、城市、位置和显示选择。
|
|
22
|
+
|
|
23
|
+
完整更新记录见 [CHANGELOG](CHANGELOG.md)。
|
|
24
|
+
|
|
5
25
|
## 安装与使用
|
|
6
26
|
|
|
7
27
|
安装一次:
|
|
@@ -26,6 +46,19 @@ easy-codex show
|
|
|
26
46
|
|
|
27
47
|
输出每个额度桶的主、次窗口。窗口时长采用服务端数据,重置时间使用本机时区。剩余比例为 `100 - usedPercent`,限制在 0–100%;未返回的字段显示“未知”。额度属于账号,不是当前会话的 Token 数,也不是 API 账单余额。即使有剩余百分比,也不代表其他服务端限制已解除。
|
|
28
48
|
|
|
49
|
+
## 升级与菜单
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npm i -g easy-codex@latest
|
|
53
|
+
easy-codex show
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`show` 从 v0.2.1 正式提供,`overlay` 保留兼容。更新前从 EC 菜单退出旧浮窗,再运行 `show`。已保存的城市、时区、位置与显示选择会保留;“恢复默认位置”只恢复位置,不重置时区。
|
|
57
|
+
|
|
58
|
+
周额度菜单示例:`周 · 77% · 5天后 25日 06:17 重置`。周倒计时只显示最大单位;若账号返回5小时窗口,其倒计时保留时分组合,例如 `3小时2分后 07:59 重置`,不足一小时仅显示分钟。绝对重置时间按电脑系统时区,独立时间浮窗按所选城市时区。
|
|
59
|
+
|
|
60
|
+
时间开关为“显示时间”;城市子菜单入口如 `洛杉矶·05:23`,点号两侧无空格。只有实际返回的额度窗口才显示对应圆环和菜单项。
|
|
61
|
+
|
|
29
62
|
## 数据与限制
|
|
30
63
|
|
|
31
64
|
通过官方 [App Server 协议](https://developers.openai.com/codex/app-server) 启动 `codex app-server --listen stdio://`,完成握手后调用 `account/rateLimits/read`,随后关闭本工具启动的进程。优先读取多额度桶,兼容单额度桶。
|
|
@@ -80,10 +113,13 @@ GitHub Actions 在 `v*` 标签上测试、构建通用 macOS 浮窗,并发布
|
|
|
80
113
|
|
|
81
114
|
## 电子时钟
|
|
82
115
|
|
|
83
|
-
透明时钟位于圆环上方,竖排居中,默认间距10px,可独立拖动。默认硅谷,城市顺序为硅谷、洛杉矶、西雅图、华盛顿特区、哥伦布(俄亥俄州)、新加坡、上海,也可自定义 IANA 时区;设置自动保存。仅显示时分,按目标当地时间00–09红色、09–
|
|
116
|
+
透明时钟位于圆环上方,竖排居中,默认间距10px,可独立拖动。默认硅谷,城市顺序为硅谷、洛杉矶、西雅图、华盛顿特区、哥伦布(俄亥俄州)、新加坡、上海,也可自定义 IANA 时区;设置自动保存。仅显示时分,按目标当地时间00–09红色、09–18绿色、18–24橙色,自动处理夏令时,不修改电脑系统时区。菜单“恢复默认位置”应用竖排默认布局;已有拖动位置继续保留。
|
|
84
117
|
|
|
85
118
|
|
|
86
119
|
|
|
87
120
|
浮窗使用 `easy-codex show` 启动;原 `overlay` 命令保留兼容。
|
|
88
121
|
|
|
89
122
|
周倒计时只显示最大单位,例如“6天后 25日 06:17 重置”,不足一天按小时、不足一小时按分钟。5小时窗口保留时分组合,例如“3小时2分后 07:59 重置”,没有小时则“22分后 07:59 重置”,省略为零的单位。绝对重置时刻使用电脑本地时区;周含日,5小时不含日期。
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
命令更新:`easy-codex update`,更新完成后运行 `easy-codex show`,会自动退出旧浮窗并打开新版。旧版本尚未提供 update 时,可先执行 `npm i -g easy-codex@latest`。更新过程中需要正常网络及原安装目录写入权限,不会自动使用 sudo。
|
package/bin/easy-codex.js
CHANGED
|
@@ -5,16 +5,19 @@ 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|show|overlay]\n显示已登录 Codex 账号的剩余百分比和重置时间。需要官方 Codex CLI。');
|
|
8
|
+
console.log('用法:easy-codex [usage|show|overlay|update]\n显示已登录 Codex 账号的剩余百分比和重置时间。需要官方 Codex CLI。');
|
|
9
9
|
} else if (args.length === 1 && ['--version', '-v'].includes(args[0])) {
|
|
10
10
|
console.log(version);
|
|
11
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
|
+
} else if (args.length === 1 && args[0] === 'update') {
|
|
15
|
+
try { const { updatePackage } = await import('../src/update.js'); await updatePackage('easy-codex'); }
|
|
16
|
+
catch (error) { console.error(`easy-codex: ${error.message}`); process.exitCode = 1; }
|
|
14
17
|
} else if (args.length === 0 || (args.length === 1 && args[0] === 'usage')) {
|
|
15
18
|
try { console.log(formatUsage(await readUsage())); }
|
|
16
19
|
catch (error) { console.error(`easy-codex: ${error.message}`); process.exitCode = 1; }
|
|
17
20
|
} else {
|
|
18
|
-
console.error('未知参数。用法:easy-codex [usage|show|overlay],或 easy-codex --help');
|
|
21
|
+
console.error('未知参数。用法:easy-codex [usage|show|overlay|update],或 easy-codex --help');
|
|
19
22
|
process.exitCode = 1;
|
|
20
23
|
}
|
package/native/Overlay.swift
CHANGED
|
@@ -196,8 +196,8 @@ func clockState(at date: Date, zone: TimeZone) -> RingState {
|
|
|
196
196
|
var calendar = Calendar(identifier: .gregorian)
|
|
197
197
|
calendar.timeZone = zone
|
|
198
198
|
let hour = calendar.component(.hour, from: date)
|
|
199
|
-
if hour >= 9 && hour <
|
|
200
|
-
if hour >=
|
|
199
|
+
if hour >= 9 && hour < 18 { return .green }
|
|
200
|
+
if hour >= 18 { return .orange }
|
|
201
201
|
return .red
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -524,10 +524,13 @@ struct Main {
|
|
|
524
524
|
precondition(clockText(at: winter, zone: singapore) == "20:00")
|
|
525
525
|
precondition(TimeZone(identifier: "Invalid/Zone") == nil)
|
|
526
526
|
let utc = TimeZone(secondsFromGMT: 0)!
|
|
527
|
-
for (hour, state) in [(0, RingState.red), (6, .red), (8, .red), (9, .green), (14, .green), (15, .orange), (23, .orange)] {
|
|
527
|
+
for (hour, state) in [(0, RingState.red), (6, .red), (8, .red), (9, .green), (14, .green), (15, .green), (17, .green), (18, .orange), (23, .orange)] {
|
|
528
528
|
let date = Date(timeIntervalSince1970: Double(hour * 3600))
|
|
529
529
|
precondition(clockState(at: date, zone: utc) == state)
|
|
530
530
|
}
|
|
531
|
+
for (seconds, state) in [(32399.0, RingState.red), (32400.0, .green), (64799.0, .green), (64800.0, .orange), (86400.0, .red)] {
|
|
532
|
+
precondition(clockState(at: Date(timeIntervalSince1970: seconds), zone: utc) == state)
|
|
533
|
+
}
|
|
531
534
|
precondition(clockState(at: winter, zone: la) == .red)
|
|
532
535
|
precondition(clockState(at: winter, zone: singapore) == .orange)
|
|
533
536
|
assert(RingState.resolve(51, stale: false) == .green)
|
|
@@ -544,6 +547,20 @@ struct Main {
|
|
|
544
547
|
print("Swift 圆环颜色与窗口坐标检查通过")
|
|
545
548
|
return
|
|
546
549
|
}
|
|
550
|
+
if CommandLine.arguments.contains("--quit-running") {
|
|
551
|
+
let running = NSRunningApplication.runningApplications(withBundleIdentifier: "com.unionschool.easy-codex.overlay")
|
|
552
|
+
.filter { $0.processIdentifier != ProcessInfo.processInfo.processIdentifier }
|
|
553
|
+
for app in running { _ = app.terminate() }
|
|
554
|
+
let deadline = Date().addingTimeInterval(5)
|
|
555
|
+
while running.contains(where: { !$0.isTerminated }), Date() < deadline {
|
|
556
|
+
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
|
|
557
|
+
}
|
|
558
|
+
if running.contains(where: { !$0.isTerminated }) {
|
|
559
|
+
fputs("旧浮窗尚未退出,请从菜单退出后重试。\n", stderr)
|
|
560
|
+
exit(1)
|
|
561
|
+
}
|
|
562
|
+
return
|
|
563
|
+
}
|
|
547
564
|
let app = NSApplication.shared
|
|
548
565
|
app.setActivationPolicy(.accessory)
|
|
549
566
|
if CommandLine.arguments.contains("--diagnose") {
|
|
@@ -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.
|
|
8
|
+
<key>CFBundleShortVersionString</key><string>0.2.2</string>
|
|
9
9
|
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
|
10
10
|
<key>LSUIElement</key><true/>
|
|
11
11
|
<key>NSHighResolutionCapable</key><true/>
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "easy-codex",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Codex usage limits and transparent world clock overlay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"README.md",
|
|
13
13
|
"native/Overlay.swift",
|
|
14
14
|
"native/build/EasyCodexOverlay.app",
|
|
15
|
-
"scripts/build-overlay.mjs"
|
|
15
|
+
"scripts/build-overlay.mjs",
|
|
16
|
+
"CHANGELOG.md"
|
|
16
17
|
],
|
|
17
18
|
"engines": {
|
|
18
19
|
"node": ">=22"
|
package/src/overlay.js
CHANGED
|
@@ -4,12 +4,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
|
|
7
|
-
export async function launchOverlay() {
|
|
8
|
-
if (
|
|
7
|
+
export async function launchOverlay({ platform = process.platform, check = access, run = promisify(execFile) } = {}) {
|
|
8
|
+
if (platform !== 'darwin') throw new Error('浮窗首版仅支持 macOS。');
|
|
9
9
|
const app = fileURLToPath(new URL('../native/build/EasyCodexOverlay.app', import.meta.url));
|
|
10
|
-
try { await
|
|
10
|
+
try { await check(`${app}/Contents/MacOS/EasyCodexOverlay`, constants.X_OK); }
|
|
11
11
|
catch { throw new Error('尚未构建浮窗,请在项目目录执行 npm run build:overlay。'); }
|
|
12
|
-
await
|
|
12
|
+
try { await run(`${app}/Contents/MacOS/EasyCodexOverlay`, ['--quit-running'], { timeout: 7000, maxBuffer: 4096 }); }
|
|
13
|
+
catch { throw new Error('无法退出旧浮窗,请从菜单退出后重试。'); }
|
|
14
|
+
await run('/usr/bin/open', ['-g', '-a', app, '--args',
|
|
13
15
|
process.execPath, fileURLToPath(new URL('./overlay-query.js', import.meta.url)), process.env.PATH ?? '', process.env.CODEX_HOME ?? '']);
|
|
14
16
|
console.log('额度与电子时钟浮窗已启动;Codex 在前台时显示。拖动调整位置,右键或菜单栏 EC 可刷新、切换中心额度、设置时区和退出。');
|
|
15
17
|
}
|
package/src/update.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { access, realpath } from 'node:fs/promises';
|
|
2
|
+
import { dirname, basename, join, delimiter } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
export function installedPrefix(root, platform = process.platform) {
|
|
7
|
+
const modules = dirname(root.replace(/[\\/]$/, ''));
|
|
8
|
+
if (basename(modules) !== 'node_modules') return null;
|
|
9
|
+
const parent = dirname(modules);
|
|
10
|
+
return basename(parent) === 'lib' ? dirname(parent) : platform === 'win32' ? parent : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function findNpmCli() {
|
|
14
|
+
const directories = [dirname(process.execPath), ...(process.env.PATH ?? '').split(delimiter)];
|
|
15
|
+
for (const directory of directories.filter(Boolean)) {
|
|
16
|
+
for (const path of [join(directory, '../lib/node_modules/npm/bin/npm-cli.js'),
|
|
17
|
+
join(directory, 'node_modules/npm/bin/npm-cli.js'), join(directory, 'npm')]) {
|
|
18
|
+
try { const resolved = await realpath(path); await access(resolved); return resolved; }
|
|
19
|
+
catch { /* npm 可能位于 PATH 中另一套 Node 的旁边。 */ }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
throw new Error('未找到 npm,请先安装 Node.js/npm,再使用 npm i -g 更新。');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function runNpm(command, args) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const child = spawn(command, args, { stdio: 'inherit', shell: false });
|
|
28
|
+
child.once('error', () => reject(new Error('无法启动 npm 更新。')));
|
|
29
|
+
child.once('exit', (code, signal) => {
|
|
30
|
+
if (code === 0) resolve();
|
|
31
|
+
else reject(new Error(`npm 更新失败(${signal ?? code ?? '未知状态'}),请检查网络或安装目录权限。`));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function updatePackage(name, { root = fileURLToPath(new URL('../', import.meta.url)),
|
|
37
|
+
getNpmCli = findNpmCli, run = runNpm, platform = process.platform } = {}) {
|
|
38
|
+
if (!['easy-codex', 'easy-claude'].includes(name)) throw new Error('不支持的更新包名。');
|
|
39
|
+
const npmCli = await getNpmCli();
|
|
40
|
+
const prefix = installedPrefix(root, platform);
|
|
41
|
+
const args = [npmCli, 'install', '--global', ...(prefix ? ['--prefix', prefix] : []),
|
|
42
|
+
'--registry=https://registry.npmjs.org', `${name}@latest`];
|
|
43
|
+
await run(process.execPath, args);
|
|
44
|
+
console.log(`${name} 已更新;运行 ${name} show 自动重启浮窗。`);
|
|
45
|
+
}
|