@openxiaobu/codexl 0.1.1 → 0.1.3
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 +7 -14
- package/dist/account-store.js +28 -13
- package/dist/cli.js +57 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- Fetch the latest usage from the official usage endpoint
|
|
12
12
|
- Expose a local provider endpoint for Codex
|
|
13
13
|
- Apply local cooldown rules for temporary, 5-hour, and weekly limits
|
|
14
|
-
-
|
|
14
|
+
- Automatically switch `~/.codex/config.toml` to the `codexl` provider while the local proxy is running
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
@@ -51,16 +51,10 @@ codexl start
|
|
|
51
51
|
codexl start --port 4399
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
`start` will automatically write the required provider config into `~/.codex/config.toml`:
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
codexl
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Write provider config into `~/.codex/config.toml`:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
codexl config
|
|
57
|
+
codexl start
|
|
64
58
|
```
|
|
65
59
|
|
|
66
60
|
## Commands
|
|
@@ -72,8 +66,6 @@ codexl import <name> [HOME]
|
|
|
72
66
|
codexl status
|
|
73
67
|
codexl start [--port <port>]
|
|
74
68
|
codexl stop
|
|
75
|
-
codexl get
|
|
76
|
-
codexl config [codexPath]
|
|
77
69
|
```
|
|
78
70
|
|
|
79
71
|
## How `status` Works
|
|
@@ -87,9 +79,9 @@ Instead it:
|
|
|
87
79
|
3. Stores the latest result in `~/.codexl/state.json`
|
|
88
80
|
4. Renders the latest local cache
|
|
89
81
|
|
|
90
|
-
##
|
|
82
|
+
## Managed Codex Config
|
|
91
83
|
|
|
92
|
-
`codexl
|
|
84
|
+
`codexl start` writes a managed provider block like this:
|
|
93
85
|
|
|
94
86
|
```toml
|
|
95
87
|
# >>> codexl managed start >>>
|
|
@@ -107,7 +99,8 @@ Behavior:
|
|
|
107
99
|
- If global `model_provider` exists, it is changed to `codexl`
|
|
108
100
|
- If commented `# model_provider = ...` exists, it is reopened as `model_provider = "codexl"`
|
|
109
101
|
- Global `model` is kept unchanged
|
|
110
|
-
- If you start with `--port`, the port is saved to `~/.codexl/config.yaml
|
|
102
|
+
- If you start with `--port`, the port is saved to `~/.codexl/config.yaml`
|
|
103
|
+
- `codexl stop` comments out the active `model_provider = "codexl"` line and keeps the rest of the file unchanged
|
|
111
104
|
|
|
112
105
|
## Data Directory
|
|
113
106
|
|
package/dist/account-store.js
CHANGED
|
@@ -51,6 +51,25 @@ function readAuthFile(codexHome) {
|
|
|
51
51
|
}
|
|
52
52
|
return JSON.parse(node_fs_1.default.readFileSync(authPath, "utf8"));
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* 从 `id_token` 中解析邮箱。
|
|
56
|
+
*
|
|
57
|
+
* @param auth 认证文件对象。
|
|
58
|
+
* @returns 邮箱地址;缺失或解析失败时返回 `undefined`。
|
|
59
|
+
*/
|
|
60
|
+
function resolveEmailFromAuth(auth) {
|
|
61
|
+
const idToken = auth?.tokens?.id_token;
|
|
62
|
+
if (!idToken) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const payload = JSON.parse(Buffer.from(idToken.split(".")[1] ?? "", "base64url").toString("utf8"));
|
|
67
|
+
return payload.email;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
54
73
|
/**
|
|
55
74
|
* 将来源 HOME 下的官方 `.codex` 登录态复制到目标 HOME。
|
|
56
75
|
*
|
|
@@ -92,23 +111,18 @@ function cloneCodexAuthState(sourceHome, targetHome) {
|
|
|
92
111
|
*
|
|
93
112
|
* 完整标准:
|
|
94
113
|
* 1. 存在 `.codex/auth.json`
|
|
95
|
-
* 2.
|
|
96
|
-
* 3.
|
|
114
|
+
* 2. `auth.json` 中存在 `access_token`
|
|
115
|
+
* 3. `auth.json` 中存在 `refresh_token`
|
|
116
|
+
* 4. `auth.json` 中存在 `account_id`
|
|
97
117
|
*
|
|
98
118
|
* @param codexHome 待检查的 HOME 目录。
|
|
99
119
|
* @returns 为 `true` 表示登录态完整,可用于调度;否则为 `false`。
|
|
100
120
|
*/
|
|
101
121
|
function hasCompleteCodexAuthState(codexHome) {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (!node_fs_1.default.existsSync(authPath) || !node_fs_1.default.existsSync(registryPath) || !node_fs_1.default.existsSync(accountsDir)) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
return node_fs_1.default
|
|
110
|
-
.readdirSync(accountsDir, { withFileTypes: true })
|
|
111
|
-
.some((entry) => entry.isFile() && entry.name.endsWith(".auth.json"));
|
|
122
|
+
const auth = readAuthFile(codexHome);
|
|
123
|
+
return Boolean(auth?.tokens?.access_token &&
|
|
124
|
+
auth?.tokens?.refresh_token &&
|
|
125
|
+
auth?.tokens?.account_id);
|
|
112
126
|
}
|
|
113
127
|
/**
|
|
114
128
|
* 将最新认证信息回写到指定账号的 `auth.json`。
|
|
@@ -153,11 +167,12 @@ function registerManagedAccount(accountId, codexHome) {
|
|
|
153
167
|
// 预先创建账号隔离目录,方便后续直接执行 codex login。
|
|
154
168
|
node_fs_1.default.mkdirSync(home, { recursive: true });
|
|
155
169
|
const primary = resolvePrimaryRegistryAccount(home);
|
|
170
|
+
const auth = readAuthFile(home);
|
|
156
171
|
const account = {
|
|
157
172
|
id: accountId,
|
|
158
173
|
name: accountId,
|
|
159
174
|
codex_home: home,
|
|
160
|
-
email: primary?.email,
|
|
175
|
+
email: primary?.email ?? resolveEmailFromAuth(auth),
|
|
161
176
|
enabled: true,
|
|
162
177
|
imported_at: new Date().toISOString()
|
|
163
178
|
};
|
package/dist/cli.js
CHANGED
|
@@ -115,6 +115,7 @@ async function handleStart(portOverride) {
|
|
|
115
115
|
}
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
|
+
applyManagedCodexConfig();
|
|
118
119
|
const logPath = (0, config_1.getServiceLogPath)();
|
|
119
120
|
const logFd = node_fs_1.default.openSync(logPath, "a");
|
|
120
121
|
const child = (0, node_child_process_1.spawn)(process.execPath, [__filename.replace(/cli\.js$/, "serve.js"), "--port", String(port)], {
|
|
@@ -136,38 +137,41 @@ function handleStop() {
|
|
|
136
137
|
const pid = getRunningPid();
|
|
137
138
|
if (!pid) {
|
|
138
139
|
console.log("服务未运行");
|
|
140
|
+
deactivateManagedCodexConfig();
|
|
139
141
|
return;
|
|
140
142
|
}
|
|
141
143
|
process.kill(pid, "SIGTERM");
|
|
142
144
|
node_fs_1.default.rmSync((0, config_1.getPidPath)(), { force: true });
|
|
145
|
+
deactivateManagedCodexConfig();
|
|
143
146
|
console.log(`服务已停止,PID=${pid}`);
|
|
144
147
|
}
|
|
145
148
|
/**
|
|
146
|
-
*
|
|
149
|
+
* 对正则元字符做转义,供动态构造匹配模式使用。
|
|
147
150
|
*
|
|
148
|
-
* @
|
|
151
|
+
* @param input 原始字符串。
|
|
152
|
+
* @returns 经过转义后的安全正则片段。
|
|
149
153
|
*/
|
|
150
|
-
function handleGetConfig() {
|
|
151
|
-
const config = (0, config_1.loadConfig)();
|
|
152
|
-
console.log(`base_url=${`http://${config.server.host}:${config.server.port}/v1`}`);
|
|
153
|
-
console.log(`api_key=${config.server.api_key}`);
|
|
154
|
-
}
|
|
155
154
|
function escapeRegExp(input) {
|
|
156
155
|
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
157
156
|
}
|
|
158
157
|
/**
|
|
159
|
-
*
|
|
158
|
+
* 返回默认的 `codex config.toml` 路径。
|
|
160
159
|
*
|
|
161
|
-
* @
|
|
162
|
-
* @returns 无返回值。
|
|
160
|
+
* @returns 默认 `config.toml` 绝对路径。
|
|
163
161
|
*/
|
|
164
|
-
function
|
|
162
|
+
function getDefaultCodexConfigPath() {
|
|
163
|
+
return node_path_1.default.join(process.env.HOME ?? "", ".codex", "config.toml");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 生成 codexl 托管的 provider 配置块。
|
|
167
|
+
*
|
|
168
|
+
* @returns 可直接写入 `config.toml` 的配置块内容。
|
|
169
|
+
*/
|
|
170
|
+
function buildManagedConfigBlock() {
|
|
165
171
|
const config = (0, config_1.loadConfig)();
|
|
166
|
-
const rawTarget = targetPathOrDir ? (0, config_1.expandHome)(targetPathOrDir) : node_path_1.default.join(process.env.HOME ?? "", ".codex", "config.toml");
|
|
167
|
-
const targetFile = rawTarget.endsWith(".toml") ? rawTarget : node_path_1.default.join(rawTarget, "config.toml");
|
|
168
172
|
const startMarker = "# >>> codexl managed start >>>";
|
|
169
173
|
const endMarker = "# <<< codexl managed end <<<";
|
|
170
|
-
|
|
174
|
+
return [
|
|
171
175
|
startMarker,
|
|
172
176
|
"[model_providers.codexl]",
|
|
173
177
|
'name = "codexl"',
|
|
@@ -176,6 +180,19 @@ function handleConfig(targetPathOrDir) {
|
|
|
176
180
|
'wire_api = "responses"',
|
|
177
181
|
endMarker
|
|
178
182
|
].join("\n");
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 将 codexl provider 配置写入指定的 codex config.toml。
|
|
186
|
+
*
|
|
187
|
+
* @param targetPathOrDir 可选的 codex 配置目录或 config.toml 文件路径。
|
|
188
|
+
* @returns 实际写入的 `config.toml` 文件路径。
|
|
189
|
+
*/
|
|
190
|
+
function applyManagedCodexConfig(targetPathOrDir, options) {
|
|
191
|
+
const rawTarget = targetPathOrDir ? (0, config_1.expandHome)(targetPathOrDir) : getDefaultCodexConfigPath();
|
|
192
|
+
const targetFile = rawTarget.endsWith(".toml") ? rawTarget : node_path_1.default.join(rawTarget, "config.toml");
|
|
193
|
+
const startMarker = "# >>> codexl managed start >>>";
|
|
194
|
+
const endMarker = "# <<< codexl managed end <<<";
|
|
195
|
+
const block = buildManagedConfigBlock();
|
|
179
196
|
let original = "";
|
|
180
197
|
if (node_fs_1.default.existsSync(targetFile)) {
|
|
181
198
|
original = node_fs_1.default.readFileSync(targetFile, "utf8");
|
|
@@ -240,10 +257,31 @@ function handleConfig(targetPathOrDir) {
|
|
|
240
257
|
}
|
|
241
258
|
const nextContent = `${lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
|
|
242
259
|
node_fs_1.default.writeFileSync(targetFile, nextContent, "utf8");
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
260
|
+
if (!options?.silent) {
|
|
261
|
+
const config = (0, config_1.loadConfig)();
|
|
262
|
+
console.log(`已写入: ${targetFile}`);
|
|
263
|
+
console.log(`base_url=http://${config.server.host}:${config.server.port}/v1`);
|
|
264
|
+
console.log(`api_key=${config.server.api_key}`);
|
|
265
|
+
console.log("提示: start 会自动接管 codex provider,stop 会自动恢复。");
|
|
266
|
+
}
|
|
267
|
+
return targetFile;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* 关闭 codexl 作为当前默认 provider 的接管状态。
|
|
271
|
+
*
|
|
272
|
+
* @returns 无返回值。
|
|
273
|
+
*/
|
|
274
|
+
function deactivateManagedCodexConfig() {
|
|
275
|
+
const targetFile = getDefaultCodexConfigPath();
|
|
276
|
+
if (!node_fs_1.default.existsSync(targetFile)) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const original = node_fs_1.default.readFileSync(targetFile, "utf8");
|
|
280
|
+
const nextContent = original.replace(/^(\s*)model_provider\s*=\s*"codexl"\s*$/m, '$1# model_provider = "codexl"');
|
|
281
|
+
if (nextContent !== original) {
|
|
282
|
+
node_fs_1.default.writeFileSync(targetFile, nextContent, "utf8");
|
|
283
|
+
console.log(`已更新: ${targetFile}`);
|
|
284
|
+
}
|
|
247
285
|
}
|
|
248
286
|
/**
|
|
249
287
|
* CLI 主入口,负责命令注册与执行。
|
|
@@ -256,10 +294,9 @@ async function main() {
|
|
|
256
294
|
(0, config_1.getCodexSwHome)();
|
|
257
295
|
(0, config_1.loadConfig)();
|
|
258
296
|
program
|
|
259
|
-
.name("codexsw")
|
|
260
297
|
.name("codexl")
|
|
261
298
|
.description("本地 Codex 多账号切换与状态管理工具")
|
|
262
|
-
.version("0.1.
|
|
299
|
+
.version("0.1.2");
|
|
263
300
|
program
|
|
264
301
|
.command("add")
|
|
265
302
|
.description("登录并新增一个账号或工作空间")
|
|
@@ -292,12 +329,6 @@ async function main() {
|
|
|
292
329
|
await handleStart(options.port);
|
|
293
330
|
});
|
|
294
331
|
program.command("stop").description("停止后台代理服务").action(handleStop);
|
|
295
|
-
program.command("get").description("输出当前 base_url 和 api_key").action(handleGetConfig);
|
|
296
|
-
program
|
|
297
|
-
.command("config")
|
|
298
|
-
.description("自动写入 codex 的 config.toml,默认 ~/.codex/config.toml")
|
|
299
|
-
.argument("[codexPath]", "codex 配置目录或 config.toml 文件路径")
|
|
300
|
-
.action(handleConfig);
|
|
301
332
|
await program.parseAsync(process.argv);
|
|
302
333
|
}
|
|
303
334
|
void main().catch((error) => {
|