codex-slot 0.1.23 → 0.1.25
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 +2 -0
- package/dist/app/service-lifecycle-service.js +17 -8
- package/dist/app/status-service.js +2 -0
- package/dist/codex-config.js +2 -1
- package/dist/state.js +31 -1
- package/dist/status-command.js +74 -10
- package/dist/status.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,6 +133,7 @@ Instead it:
|
|
|
133
133
|
name = "cslot"
|
|
134
134
|
base_url = "http://127.0.0.1:4399/v1"
|
|
135
135
|
wire_api = "responses"
|
|
136
|
+
requires_openai_auth = true
|
|
136
137
|
```
|
|
137
138
|
|
|
138
139
|
Behavior:
|
|
@@ -142,6 +143,7 @@ Behavior:
|
|
|
142
143
|
- Other providers and settings in `config.toml` are left untouched
|
|
143
144
|
- If you start with `--port`, the port is saved to `~/.cslot/config.yaml`
|
|
144
145
|
- If you start without `--port`, `4399` is preferred first and the next free port is chosen automatically on conflict, and the actual chosen port is written back to `~/.cslot/config.yaml` and the managed provider block
|
|
146
|
+
- `requires_openai_auth = true` keeps Codex App treating the local cslot provider as a ChatGPT-authenticated provider, so plugin navigation and trusted plugin runtimes are not disabled as API-key/custom-provider mode
|
|
145
147
|
- `/backend-api/*` requests are forwarded to ChatGPT backend with the current selected account's upstream token; client `Authorization` headers are not forwarded upstream
|
|
146
148
|
|
|
147
149
|
## Codex App Plugins
|
|
@@ -13,11 +13,11 @@ const node_child_process_1 = require("node:child_process");
|
|
|
13
13
|
const undici_1 = require("undici");
|
|
14
14
|
const account_service_1 = require("./account-service");
|
|
15
15
|
const account_store_1 = require("../account-store");
|
|
16
|
+
const state_1 = require("../state");
|
|
16
17
|
const codex_config_1 = require("../codex-config");
|
|
17
18
|
const codex_auth_1 = require("../codex-auth");
|
|
18
19
|
const cli_helpers_1 = require("../cli-helpers");
|
|
19
20
|
const config_1 = require("../config");
|
|
20
|
-
const scheduler_1 = require("../scheduler");
|
|
21
21
|
const STARTUP_POLL_INTERVAL_MS = 100;
|
|
22
22
|
const STARTUP_TIMEOUT_MS = 5000;
|
|
23
23
|
/**
|
|
@@ -173,19 +173,28 @@ function rollbackFailedStart(pid, previousConfig) {
|
|
|
173
173
|
* 选择一个可用于接管主 `~/.codex` 登录态的账号。
|
|
174
174
|
*
|
|
175
175
|
* 业务规则:
|
|
176
|
-
* 1.
|
|
177
|
-
* 2.
|
|
178
|
-
* 3.
|
|
176
|
+
* 1. 优先使用状态面板中手动选择的 Codex App 登录态账号。
|
|
177
|
+
* 2. 手动选择存在但账号缺失或登录态不完整时直接报错,避免静默切到其他账号。
|
|
178
|
+
* 3. 未手动选择时,回退到首个启用且本地工作空间仍存在的账号。
|
|
179
|
+
* 4. 若仍无可用账号,则返回 `null`,此时仅接管 provider 配置,不强行覆盖主登录态。
|
|
179
180
|
*
|
|
180
181
|
* @returns 选中的受管账号;若不存在可接管账号则返回 `null`。
|
|
181
|
-
* @throws
|
|
182
|
+
* @throws 当手动选择的账号不存在或登录态不完整时抛出错误。
|
|
182
183
|
*/
|
|
183
184
|
function resolveManagedAuthAccount() {
|
|
184
|
-
const
|
|
185
|
-
|
|
185
|
+
const accounts = (0, account_service_1.listAccounts)();
|
|
186
|
+
const selectedAuthAccountId = (0, state_1.getSelectedCodexAuthAccountId)();
|
|
187
|
+
if (selectedAuthAccountId) {
|
|
188
|
+
const selected = accounts.find((account) => account.id === selectedAuthAccountId);
|
|
189
|
+
if (!selected) {
|
|
190
|
+
throw new Error(`手动选择的 Codex App 登录态账号不存在: ${selectedAuthAccountId}`);
|
|
191
|
+
}
|
|
192
|
+
if (!node_fs_1.default.existsSync(selected.codex_home) || !(0, account_store_1.hasCompleteCodexAuthState)(selected.codex_home)) {
|
|
193
|
+
throw new Error(`手动选择的 Codex App 登录态账号缺少完整 auth.json: ${selectedAuthAccountId}`);
|
|
194
|
+
}
|
|
186
195
|
return selected;
|
|
187
196
|
}
|
|
188
|
-
return (
|
|
197
|
+
return (accounts.find((account) => account.enabled &&
|
|
189
198
|
node_fs_1.default.existsSync(account.codex_home) &&
|
|
190
199
|
(0, account_store_1.hasCompleteCodexAuthState)(account.codex_home)) ?? null);
|
|
191
200
|
}
|
|
@@ -6,6 +6,7 @@ exports.persistAccountEnabledState = persistAccountEnabledState;
|
|
|
6
6
|
const config_1 = require("../config");
|
|
7
7
|
const scheduler_1 = require("../scheduler");
|
|
8
8
|
const status_1 = require("../status");
|
|
9
|
+
const state_1 = require("../state");
|
|
9
10
|
const usage_sync_1 = require("../usage-sync");
|
|
10
11
|
/**
|
|
11
12
|
* 刷新全部账号额度并返回最新状态快照。
|
|
@@ -29,6 +30,7 @@ function getStatusSnapshot() {
|
|
|
29
30
|
return {
|
|
30
31
|
statuses,
|
|
31
32
|
selectedName: selected?.account.name ?? null,
|
|
33
|
+
codexAuthAccountId: (0, state_1.getSelectedCodexAuthAccountId)(),
|
|
32
34
|
summary: (0, status_1.summarizeAccountStatuses)(statuses)
|
|
33
35
|
};
|
|
34
36
|
}
|
package/dist/codex-config.js
CHANGED
|
@@ -71,7 +71,8 @@ function buildManagedProviderBlock(eol, config) {
|
|
|
71
71
|
"[model_providers.cslot]",
|
|
72
72
|
'name = "cslot"',
|
|
73
73
|
`base_url = "http://${config.server.host}:${config.server.port}/v1"`,
|
|
74
|
-
'wire_api = "responses"'
|
|
74
|
+
'wire_api = "responses"',
|
|
75
|
+
"requires_openai_auth = true"
|
|
75
76
|
];
|
|
76
77
|
lines.push(PROVIDER_BLOCK_END_MARKER);
|
|
77
78
|
return lines.join(eol);
|
package/dist/state.js
CHANGED
|
@@ -15,6 +15,8 @@ exports.getUsageCache = getUsageCache;
|
|
|
15
15
|
exports.setUsageRefreshError = setUsageRefreshError;
|
|
16
16
|
exports.clearUsageRefreshError = clearUsageRefreshError;
|
|
17
17
|
exports.getUsageRefreshError = getUsageRefreshError;
|
|
18
|
+
exports.getSelectedCodexAuthAccountId = getSelectedCodexAuthAccountId;
|
|
19
|
+
exports.setSelectedCodexAuthAccountId = setSelectedCodexAuthAccountId;
|
|
18
20
|
exports.getManagedCodexConfigState = getManagedCodexConfigState;
|
|
19
21
|
exports.getManagedCodexAuthState = getManagedCodexAuthState;
|
|
20
22
|
exports.setManagedCodexConfigState = setManagedCodexConfigState;
|
|
@@ -24,7 +26,7 @@ exports.clearManagedCodexAuthState = clearManagedCodexAuthState;
|
|
|
24
26
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
25
27
|
const node_path_1 = __importDefault(require("node:path"));
|
|
26
28
|
const config_1 = require("./config");
|
|
27
|
-
const STATE_SCHEMA_VERSION =
|
|
29
|
+
const STATE_SCHEMA_VERSION = 2;
|
|
28
30
|
function getStatePath() {
|
|
29
31
|
return node_path_1.default.join((0, config_1.getCslotHome)(), "state.json");
|
|
30
32
|
}
|
|
@@ -41,6 +43,7 @@ function getStatePath() {
|
|
|
41
43
|
function createDefaultState() {
|
|
42
44
|
return {
|
|
43
45
|
state_version: STATE_SCHEMA_VERSION,
|
|
46
|
+
selected_codex_auth_account_id: null,
|
|
44
47
|
account_blocks: {},
|
|
45
48
|
usage_cache: {},
|
|
46
49
|
usage_refresh_errors: {},
|
|
@@ -60,6 +63,7 @@ function normalizeState(parsed) {
|
|
|
60
63
|
const defaults = createDefaultState();
|
|
61
64
|
return {
|
|
62
65
|
state_version: STATE_SCHEMA_VERSION,
|
|
66
|
+
selected_codex_auth_account_id: parsed?.selected_codex_auth_account_id ?? defaults.selected_codex_auth_account_id,
|
|
63
67
|
account_blocks: parsed?.account_blocks ?? defaults.account_blocks,
|
|
64
68
|
usage_cache: parsed?.usage_cache ?? defaults.usage_cache,
|
|
65
69
|
usage_refresh_errors: parsed?.usage_refresh_errors ?? defaults.usage_refresh_errors,
|
|
@@ -231,6 +235,32 @@ function getUsageRefreshError(accountId) {
|
|
|
231
235
|
const state = loadState();
|
|
232
236
|
return state.usage_refresh_errors[accountId] ?? null;
|
|
233
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* 读取用户在状态面板中手动选择的 Codex App 主登录态账号。
|
|
240
|
+
*
|
|
241
|
+
* 业务含义:
|
|
242
|
+
* 1. 该选择只决定 `cslot start` 复制哪个受管账号到主 `~/.codex/auth.json`。
|
|
243
|
+
* 2. 它不参与代理请求调度,也不改变账号 enabled 状态。
|
|
244
|
+
*
|
|
245
|
+
* @returns 手动选择的账号 id;未选择时返回 `null`。
|
|
246
|
+
* @throws 当 state 文件读取或解析失败时透传底层异常。
|
|
247
|
+
*/
|
|
248
|
+
function getSelectedCodexAuthAccountId() {
|
|
249
|
+
const state = loadState();
|
|
250
|
+
return state.selected_codex_auth_account_id ?? null;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 保存用户在状态面板中手动选择的 Codex App 主登录态账号。
|
|
254
|
+
*
|
|
255
|
+
* @param accountId 账号 id;传入 `null` 表示清除手动选择并恢复默认回退规则。
|
|
256
|
+
* @returns 无返回值。
|
|
257
|
+
* @throws 当 state 文件写入失败时透传底层异常。
|
|
258
|
+
*/
|
|
259
|
+
function setSelectedCodexAuthAccountId(accountId) {
|
|
260
|
+
updateState((state) => {
|
|
261
|
+
state.selected_codex_auth_account_id = accountId;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
234
264
|
/**
|
|
235
265
|
* 读取当前记录的 Codex `config.toml` 接管快照。
|
|
236
266
|
*
|
package/dist/status-command.js
CHANGED
|
@@ -5,9 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.handleStatus = handleStatus;
|
|
7
7
|
const node_readline_1 = __importDefault(require("node:readline"));
|
|
8
|
+
const account_store_1 = require("./account-store");
|
|
8
9
|
const account_service_1 = require("./app/account-service");
|
|
9
10
|
const status_service_1 = require("./app/status-service");
|
|
10
11
|
const scheduler_1 = require("./scheduler");
|
|
12
|
+
const state_1 = require("./state");
|
|
13
|
+
const codex_auth_1 = require("./codex-auth");
|
|
11
14
|
const status_1 = require("./status");
|
|
12
15
|
const text_1 = require("./text");
|
|
13
16
|
const ANSI = {
|
|
@@ -206,16 +209,24 @@ function renderInteractiveScreen(lines) {
|
|
|
206
209
|
* 计算交互式状态面板的初始光标位置。
|
|
207
210
|
*
|
|
208
211
|
* 业务规则:
|
|
209
|
-
* 1.
|
|
210
|
-
* 2.
|
|
211
|
-
* 3.
|
|
212
|
+
* 1. 优先定位到用户手动选择的 Codex App 登录态账号。
|
|
213
|
+
* 2. 若没有手动选择,则回退到当前自动调度选中的账号。
|
|
214
|
+
* 3. 若没有自动选中账号,则回退到首个可用账号。
|
|
215
|
+
* 4. 若所有账号都不可用,则回退到首个已启用账号。
|
|
212
216
|
*
|
|
213
217
|
* @param accounts 已按展示顺序排好的账号列表。
|
|
214
218
|
* @param statuses 当前账号运行时状态快照。
|
|
219
|
+
* @param selectedAuthAccountId 用户手动选择的 Codex App 登录态账号 id。
|
|
215
220
|
* @returns 初始光标所在的数组下标。
|
|
216
221
|
* @throws 无显式抛出。
|
|
217
222
|
*/
|
|
218
|
-
function resolveInitialCursorIndex(accounts, statuses) {
|
|
223
|
+
function resolveInitialCursorIndex(accounts, statuses, selectedAuthAccountId) {
|
|
224
|
+
if (selectedAuthAccountId) {
|
|
225
|
+
const selectedAuthIndex = accounts.findIndex((account) => account.id === selectedAuthAccountId);
|
|
226
|
+
if (selectedAuthIndex >= 0) {
|
|
227
|
+
return selectedAuthIndex;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
219
230
|
const selected = (0, scheduler_1.pickBestAccount)();
|
|
220
231
|
if (selected) {
|
|
221
232
|
const selectedIndex = accounts.findIndex((account) => account.id === selected.account.id);
|
|
@@ -234,6 +245,31 @@ function resolveInitialCursorIndex(accounts, statuses) {
|
|
|
234
245
|
}
|
|
235
246
|
return 0;
|
|
236
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* 将状态面板中选中的账号立即应用为 Codex App 主登录态。
|
|
250
|
+
*
|
|
251
|
+
* 业务含义:
|
|
252
|
+
* 1. 该操作只切换主 `~/.codex/auth.json` 的来源账号,不改变代理调度顺序。
|
|
253
|
+
* 2. 被选择账号可以是 disabled,因为 enabled 只控制代理请求调度。
|
|
254
|
+
* 3. 登录态不完整时拒绝保存选择,避免下一次 `start` 静默失败或切错账号。
|
|
255
|
+
*
|
|
256
|
+
* @param account 用户在状态面板中选中的受管账号。
|
|
257
|
+
* @returns 失败时返回错误文本;成功时返回 `null`。
|
|
258
|
+
* @throws 无显式抛出;文件系统错误会转为返回文本。
|
|
259
|
+
*/
|
|
260
|
+
function applyCodexAuthSelection(account) {
|
|
261
|
+
try {
|
|
262
|
+
if (!(0, account_store_1.hasCompleteCodexAuthState)(account.codex_home)) {
|
|
263
|
+
return `账号 ${account.id} 缺少完整 auth.json`;
|
|
264
|
+
}
|
|
265
|
+
(0, state_1.setSelectedCodexAuthAccountId)(account.id);
|
|
266
|
+
(0, codex_auth_1.applyManagedCodexAuth)(account.codex_home, { sourceAccountId: account.id });
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
return error instanceof Error ? error.message : String(error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
237
273
|
/**
|
|
238
274
|
* 进入账号启用状态的交互式切换界面,并在用户确认退出后恢复终端状态。
|
|
239
275
|
*
|
|
@@ -256,7 +292,8 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
256
292
|
return;
|
|
257
293
|
}
|
|
258
294
|
const accounts = [...accountsFromConfig].sort((left, right) => left.name.localeCompare(right.name));
|
|
259
|
-
let
|
|
295
|
+
let selectedAuthAccountId = (0, state_1.getSelectedCodexAuthAccountId)();
|
|
296
|
+
let cursor = resolveInitialCursorIndex(accounts, initialStatuses ?? (0, status_1.collectAccountStatuses)(), selectedAuthAccountId);
|
|
260
297
|
let changed = false;
|
|
261
298
|
enterInteractiveScreen();
|
|
262
299
|
return await new Promise((resolve) => {
|
|
@@ -277,9 +314,10 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
277
314
|
if (!status) {
|
|
278
315
|
return null;
|
|
279
316
|
}
|
|
317
|
+
const markers = `${account.id === autoSelectedId ? "*" : ""}${account.id === selectedAuthAccountId ? "@" : ""}`;
|
|
280
318
|
return {
|
|
281
319
|
...status,
|
|
282
|
-
name:
|
|
320
|
+
name: markers ? `${status.name}${markers}` : status.name
|
|
283
321
|
};
|
|
284
322
|
})
|
|
285
323
|
.filter((item) => item !== null);
|
|
@@ -305,11 +343,12 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
305
343
|
"",
|
|
306
344
|
renderSectionHeader("summary", rightWidth, styled),
|
|
307
345
|
renderSummaryLine(summary, rightWidth < 42, styled),
|
|
308
|
-
`
|
|
346
|
+
`scheduler=${latestSnapshot.selectedName ?? "none"}`,
|
|
347
|
+
`codex_auth=${selectedAuthAccountId ?? "none"}`,
|
|
309
348
|
...(refreshStatusText ? [`refresh=${refreshStatusText}`] : []),
|
|
310
349
|
"",
|
|
311
350
|
renderSectionHeader("help", rightWidth, styled),
|
|
312
|
-
"↑/↓ move Space toggle r refresh Enter/q exit"
|
|
351
|
+
"↑/↓ move Space toggle a app-auth c clear r refresh Enter/q exit"
|
|
313
352
|
];
|
|
314
353
|
if (wideLayout) {
|
|
315
354
|
renderInteractiveScreen(renderColumns(accountLines, sideLines, 3));
|
|
@@ -367,6 +406,30 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
367
406
|
render();
|
|
368
407
|
return;
|
|
369
408
|
}
|
|
409
|
+
if (key.name === "a") {
|
|
410
|
+
const account = accounts[cursor];
|
|
411
|
+
if (!account) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const errorMessage = applyCodexAuthSelection(account);
|
|
415
|
+
if (errorMessage) {
|
|
416
|
+
refreshStatusText = errorMessage;
|
|
417
|
+
render();
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
selectedAuthAccountId = account.id;
|
|
421
|
+
refreshStatusText = `codex_auth=${account.id}`;
|
|
422
|
+
render();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (key.name === "c") {
|
|
426
|
+
selectedAuthAccountId = null;
|
|
427
|
+
(0, state_1.setSelectedCodexAuthAccountId)(null);
|
|
428
|
+
(0, codex_auth_1.deactivateManagedCodexAuth)();
|
|
429
|
+
refreshStatusText = "codex_auth=cleared";
|
|
430
|
+
render();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
370
433
|
if (key.name === "r") {
|
|
371
434
|
if (refreshing) {
|
|
372
435
|
return;
|
|
@@ -419,10 +482,11 @@ async function handleStatus(options) {
|
|
|
419
482
|
}
|
|
420
483
|
const displayStatuses = snapshot.statuses.map((item) => ({
|
|
421
484
|
...item,
|
|
422
|
-
name: item.id === (0, scheduler_1.pickBestAccount)()?.account.id ?
|
|
485
|
+
name: `${item.name}${item.id === (0, scheduler_1.pickBestAccount)()?.account.id ? "*" : ""}${item.id === snapshot.codexAuthAccountId ? "@" : ""}`
|
|
423
486
|
}));
|
|
424
487
|
console.log((0, status_1.renderStatusTable)(displayStatuses));
|
|
425
488
|
console.log("");
|
|
426
489
|
console.log(`available=${snapshot.summary.available} 5h_limited=${snapshot.summary.fiveHourLimited} weekly_limited=${snapshot.summary.weeklyLimited}`);
|
|
427
|
-
console.log(`
|
|
490
|
+
console.log(`scheduler=${snapshot.selectedName ?? "none"}`);
|
|
491
|
+
console.log(`codex_auth=${snapshot.codexAuthAccountId ?? "none"}`);
|
|
428
492
|
}
|
package/dist/status.js
CHANGED
|
@@ -201,7 +201,7 @@ function styleStatusCell(status, item, styled) {
|
|
|
201
201
|
return status;
|
|
202
202
|
}
|
|
203
203
|
/**
|
|
204
|
-
*
|
|
204
|
+
* 对当前自动调度账号或 Codex App 登录态账号的名称做轻量强调。
|
|
205
205
|
*
|
|
206
206
|
* @param name 账号展示名称。
|
|
207
207
|
* @param styled 是否启用 ANSI 样式。
|
|
@@ -209,7 +209,7 @@ function styleStatusCell(status, item, styled) {
|
|
|
209
209
|
* @throws 无显式抛出。
|
|
210
210
|
*/
|
|
211
211
|
function styleNameCell(name, styled) {
|
|
212
|
-
if (!styled || !name.
|
|
212
|
+
if (!styled || (!name.includes("*") && !name.includes("@"))) {
|
|
213
213
|
return name;
|
|
214
214
|
}
|
|
215
215
|
return styleCell(name, TABLE_ANSI.cyan, styled);
|