codex-slot 0.1.27 → 0.1.29
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 +32 -0
- package/dist/app/status-service.js +30 -0
- package/dist/cli.js +63 -0
- package/dist/codex-config.js +36 -148
- package/dist/config.js +12 -2
- package/dist/model-proxy-dispatcher.js +67 -0
- package/dist/relay-commands.js +166 -0
- package/dist/relay-proxy-service.js +160 -0
- package/dist/relay-store.js +138 -0
- package/dist/server.js +2 -2
- package/dist/state.js +50 -0
- package/dist/status-command.js +141 -19
- package/dist/status.js +72 -0
- package/package.json +1 -1
package/dist/status-command.js
CHANGED
|
@@ -245,6 +245,12 @@ function resolveInitialCursorIndex(accounts, statuses, selectedAuthAccountId) {
|
|
|
245
245
|
}
|
|
246
246
|
return 0;
|
|
247
247
|
}
|
|
248
|
+
function buildInteractiveItems(accounts, relaySlots) {
|
|
249
|
+
return [
|
|
250
|
+
...accounts.map((account) => ({ type: "account", id: account.id })),
|
|
251
|
+
...relaySlots.map((slot) => ({ type: "relay", id: slot.id }))
|
|
252
|
+
];
|
|
253
|
+
}
|
|
248
254
|
/**
|
|
249
255
|
* 将状态面板中选中的账号立即应用为 Codex App 主登录态。
|
|
250
256
|
*
|
|
@@ -286,15 +292,27 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
286
292
|
node_readline_1.default.emitKeypressEvents(stdin);
|
|
287
293
|
stdin.setRawMode?.(true);
|
|
288
294
|
const accountsFromConfig = (0, account_service_1.listAccounts)();
|
|
289
|
-
|
|
290
|
-
|
|
295
|
+
const latestSnapshotForRelays = (0, status_service_1.getStatusSnapshot)();
|
|
296
|
+
const relaySlotsFromConfig = latestSnapshotForRelays.relaySlots;
|
|
297
|
+
if (accountsFromConfig.length === 0 && relaySlotsFromConfig.length === 0) {
|
|
298
|
+
console.log((0, text_1.bi)("当前没有已录入账号或中转槽位。", "No managed accounts or relay slots found."));
|
|
291
299
|
stdin.setRawMode?.(false);
|
|
292
300
|
return;
|
|
293
301
|
}
|
|
294
302
|
const accounts = [...accountsFromConfig].sort((left, right) => left.name.localeCompare(right.name));
|
|
303
|
+
const relaySlots = [...relaySlotsFromConfig].sort((left, right) => left.name.localeCompare(right.name));
|
|
295
304
|
let selectedAuthAccountId = (0, state_1.getSelectedCodexAuthAccountId)();
|
|
296
|
-
|
|
297
|
-
|
|
305
|
+
const initialItems = buildInteractiveItems(accounts, relaySlots);
|
|
306
|
+
const selectedModelRoute = (0, state_1.getSelectedModelRoute)();
|
|
307
|
+
const initialAccountCursor = resolveInitialCursorIndex(accounts, initialStatuses ?? (0, status_1.collectAccountStatuses)(), selectedAuthAccountId);
|
|
308
|
+
const selectedRelayIndex = selectedModelRoute.mode === "relay_slot"
|
|
309
|
+
? relaySlots.findIndex((slot) => slot.id === selectedModelRoute.relay_slot_id)
|
|
310
|
+
: -1;
|
|
311
|
+
let cursor = selectedRelayIndex >= 0
|
|
312
|
+
? accounts.length + selectedRelayIndex
|
|
313
|
+
: Math.min(initialAccountCursor, Math.max(0, initialItems.length - 1));
|
|
314
|
+
let accountChanged = false;
|
|
315
|
+
let relayChanged = false;
|
|
298
316
|
enterInteractiveScreen();
|
|
299
317
|
return await new Promise((resolve) => {
|
|
300
318
|
let closed = false;
|
|
@@ -304,7 +322,9 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
304
322
|
const screenWidth = process.stdout.columns ?? 80;
|
|
305
323
|
const styled = shouldUseAnsiStyle();
|
|
306
324
|
const latestSnapshot = (0, status_service_1.getStatusSnapshot)();
|
|
307
|
-
const
|
|
325
|
+
const items = buildInteractiveItems(accounts, relaySlots);
|
|
326
|
+
const currentSelection = items[cursor] ?? null;
|
|
327
|
+
const statusSource = accountChanged ? latestSnapshot.statuses : (initialStatuses ?? latestSnapshot.statuses);
|
|
308
328
|
const statusById = new Map(statusSource.map((item) => [item.id, item]));
|
|
309
329
|
const autoSelectedId = (0, scheduler_1.pickBestAccount)()?.account.id ?? null;
|
|
310
330
|
const summary = (0, status_1.summarizeAccountStatuses)(statusSource);
|
|
@@ -321,7 +341,20 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
321
341
|
};
|
|
322
342
|
})
|
|
323
343
|
.filter((item) => item !== null);
|
|
324
|
-
const
|
|
344
|
+
const displayRelays = relaySlots.map((slot) => {
|
|
345
|
+
const selected = latestSnapshot.modelRoute.mode === "relay_slot" &&
|
|
346
|
+
latestSnapshot.modelRoute.relay_slot_id === slot.id;
|
|
347
|
+
return {
|
|
348
|
+
...slot,
|
|
349
|
+
name: selected ? `${slot.name}*` : slot.name
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
const currentAccount = currentSelection?.type === "account"
|
|
353
|
+
? displayStatuses.find((item) => item.id === currentSelection.id) ?? null
|
|
354
|
+
: null;
|
|
355
|
+
const currentRelay = currentSelection?.type === "relay"
|
|
356
|
+
? displayRelays.find((item) => item.id === currentSelection.id) ?? null
|
|
357
|
+
: null;
|
|
325
358
|
const wideLayout = screenWidth >= 104;
|
|
326
359
|
const leftWidth = wideLayout ? Math.max(68, Math.floor(screenWidth * 0.64)) : screenWidth;
|
|
327
360
|
const rightWidth = wideLayout ? Math.max(28, screenWidth - leftWidth - 3) : screenWidth;
|
|
@@ -333,41 +366,71 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
333
366
|
styled,
|
|
334
367
|
selectorColumn: {
|
|
335
368
|
enabledById: Object.fromEntries(accounts.map((account) => [account.id, account.enabled])),
|
|
336
|
-
cursorAccountId:
|
|
369
|
+
cursorAccountId: currentSelection?.type === "account" ? currentSelection.id : null
|
|
337
370
|
}
|
|
338
371
|
}).split("\n")
|
|
339
372
|
];
|
|
373
|
+
const relayLines = [
|
|
374
|
+
renderSectionHeader("relays", leftWidth, styled),
|
|
375
|
+
...(displayRelays.length > 0
|
|
376
|
+
? (0, status_1.renderRelayStatusTable)(displayRelays, {
|
|
377
|
+
compact: true,
|
|
378
|
+
maxWidth: leftWidth,
|
|
379
|
+
styled,
|
|
380
|
+
selectorColumn: {
|
|
381
|
+
enabledById: Object.fromEntries(relaySlots.map((slot) => [slot.id, slot.enabled])),
|
|
382
|
+
cursorRelayId: currentSelection?.type === "relay" ? currentSelection.id : null
|
|
383
|
+
}
|
|
384
|
+
}).split("\n")
|
|
385
|
+
: ["-"])
|
|
386
|
+
];
|
|
387
|
+
const currentDetails = currentSelection?.type === "relay"
|
|
388
|
+
? (0, status_1.renderRelayStatusDetails)(currentRelay, { maxWidth: rightWidth, header: false }).split("\n")
|
|
389
|
+
: (0, status_1.renderStatusDetails)(currentAccount, { maxWidth: rightWidth, header: false }).split("\n");
|
|
340
390
|
const sideLines = [
|
|
341
391
|
renderSectionHeader("current", rightWidth, styled),
|
|
342
|
-
...
|
|
392
|
+
...currentDetails,
|
|
343
393
|
"",
|
|
344
394
|
renderSectionHeader("summary", rightWidth, styled),
|
|
345
395
|
renderSummaryLine(summary, rightWidth < 42, styled),
|
|
396
|
+
`model_route=${latestSnapshot.modelRouteLabel}`,
|
|
346
397
|
`scheduler=${latestSnapshot.selectedName ?? "none"}`,
|
|
347
398
|
`codex_auth=${selectedAuthAccountId ?? "none"}`,
|
|
399
|
+
`relay_slots=${latestSnapshot.relaySlots.length}`,
|
|
348
400
|
...(refreshStatusText ? [`refresh=${refreshStatusText}`] : []),
|
|
349
401
|
"",
|
|
350
402
|
renderSectionHeader("help", rightWidth, styled),
|
|
351
|
-
"↑/↓ move Space toggle a app-auth c clear r refresh Enter/q exit"
|
|
403
|
+
"↑/↓ move Space toggle a app-auth m model-route c clear r refresh Enter/q exit"
|
|
404
|
+
];
|
|
405
|
+
const leftLines = [
|
|
406
|
+
...accountLines,
|
|
407
|
+
"",
|
|
408
|
+
...relayLines
|
|
352
409
|
];
|
|
353
410
|
if (wideLayout) {
|
|
354
|
-
renderInteractiveScreen(renderColumns(
|
|
411
|
+
renderInteractiveScreen(renderColumns(leftLines, sideLines, 3));
|
|
355
412
|
return;
|
|
356
413
|
}
|
|
357
414
|
renderInteractiveScreen([
|
|
358
|
-
...
|
|
415
|
+
...leftLines,
|
|
359
416
|
"",
|
|
360
417
|
renderDivider(screenWidth, styled),
|
|
361
418
|
...sideLines
|
|
362
419
|
]);
|
|
363
420
|
};
|
|
364
421
|
const applyChanges = () => {
|
|
365
|
-
if (!
|
|
422
|
+
if (!accountChanged && !relayChanged) {
|
|
366
423
|
return;
|
|
367
424
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
425
|
+
if (accountChanged) {
|
|
426
|
+
(0, status_service_1.persistAccountEnabledState)(accounts);
|
|
427
|
+
accountChanged = false;
|
|
428
|
+
initialStatuses = (0, status_1.collectAccountStatuses)();
|
|
429
|
+
}
|
|
430
|
+
if (relayChanged) {
|
|
431
|
+
(0, status_service_1.persistRelayEnabledState)(relaySlots);
|
|
432
|
+
relayChanged = false;
|
|
433
|
+
}
|
|
371
434
|
};
|
|
372
435
|
const exitInteractive = () => {
|
|
373
436
|
if (closed) {
|
|
@@ -392,7 +455,7 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
392
455
|
return;
|
|
393
456
|
}
|
|
394
457
|
if (key.name === "down") {
|
|
395
|
-
const nextCursor = Math.min(accounts.length - 1, cursor + 1);
|
|
458
|
+
const nextCursor = Math.min(buildInteractiveItems(accounts, relaySlots).length - 1, cursor + 1);
|
|
396
459
|
if (nextCursor !== cursor) {
|
|
397
460
|
cursor = nextCursor;
|
|
398
461
|
render();
|
|
@@ -400,14 +463,33 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
400
463
|
return;
|
|
401
464
|
}
|
|
402
465
|
if (key.name === "space") {
|
|
403
|
-
|
|
404
|
-
|
|
466
|
+
const item = buildInteractiveItems(accounts, relaySlots)[cursor];
|
|
467
|
+
if (item?.type === "account") {
|
|
468
|
+
const account = accounts.find((candidate) => candidate.id === item.id);
|
|
469
|
+
if (account) {
|
|
470
|
+
account.enabled = !account.enabled;
|
|
471
|
+
accountChanged = true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
else if (item?.type === "relay") {
|
|
475
|
+
const slot = relaySlots.find((candidate) => candidate.id === item.id);
|
|
476
|
+
if (slot) {
|
|
477
|
+
slot.enabled = !slot.enabled;
|
|
478
|
+
relayChanged = true;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
405
481
|
applyChanges();
|
|
406
482
|
render();
|
|
407
483
|
return;
|
|
408
484
|
}
|
|
409
485
|
if (key.name === "a") {
|
|
410
|
-
const
|
|
486
|
+
const item = buildInteractiveItems(accounts, relaySlots)[cursor];
|
|
487
|
+
if (item?.type !== "account") {
|
|
488
|
+
refreshStatusText = "app-auth requires account";
|
|
489
|
+
render();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const account = accounts.find((candidate) => candidate.id === item.id);
|
|
411
493
|
if (!account) {
|
|
412
494
|
return;
|
|
413
495
|
}
|
|
@@ -422,6 +504,33 @@ async function handleInteractiveToggle(initialStatuses) {
|
|
|
422
504
|
render();
|
|
423
505
|
return;
|
|
424
506
|
}
|
|
507
|
+
if (key.name === "m") {
|
|
508
|
+
const item = buildInteractiveItems(accounts, relaySlots)[cursor];
|
|
509
|
+
if (item?.type === "relay") {
|
|
510
|
+
const slot = relaySlots.find((candidate) => candidate.id === item.id);
|
|
511
|
+
if (!slot) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (!slot.enabled) {
|
|
515
|
+
refreshStatusText = `relay_disabled=${slot.id}`;
|
|
516
|
+
render();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
(0, state_1.setSelectedModelRoute)({
|
|
520
|
+
mode: "relay_slot",
|
|
521
|
+
relay_slot_id: slot.id
|
|
522
|
+
});
|
|
523
|
+
refreshStatusText = `model_route=relay:${slot.id}`;
|
|
524
|
+
render();
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
(0, state_1.setSelectedModelRoute)({
|
|
528
|
+
mode: "auth_pool"
|
|
529
|
+
});
|
|
530
|
+
refreshStatusText = "model_route=auth_pool";
|
|
531
|
+
render();
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
425
534
|
if (key.name === "c") {
|
|
426
535
|
selectedAuthAccountId = null;
|
|
427
536
|
(0, state_1.setSelectedCodexAuthAccountId)(null);
|
|
@@ -485,8 +594,21 @@ async function handleStatus(options) {
|
|
|
485
594
|
name: `${item.name}${item.id === (0, scheduler_1.pickBestAccount)()?.account.id ? "*" : ""}${item.id === snapshot.codexAuthAccountId ? "@" : ""}`
|
|
486
595
|
}));
|
|
487
596
|
console.log((0, status_1.renderStatusTable)(displayStatuses));
|
|
597
|
+
if (snapshot.relaySlots.length > 0) {
|
|
598
|
+
const displayRelays = snapshot.relaySlots.map((slot) => ({
|
|
599
|
+
...slot,
|
|
600
|
+
name: snapshot.modelRoute.mode === "relay_slot" &&
|
|
601
|
+
snapshot.modelRoute.relay_slot_id === slot.id
|
|
602
|
+
? `${slot.name}*`
|
|
603
|
+
: slot.name
|
|
604
|
+
}));
|
|
605
|
+
console.log("");
|
|
606
|
+
console.log((0, status_1.renderRelayStatusTable)(displayRelays));
|
|
607
|
+
}
|
|
488
608
|
console.log("");
|
|
489
609
|
console.log(`available=${snapshot.summary.available} 5h_limited=${snapshot.summary.fiveHourLimited} weekly_limited=${snapshot.summary.weeklyLimited}`);
|
|
610
|
+
console.log(`model_route=${snapshot.modelRouteLabel}`);
|
|
490
611
|
console.log(`scheduler=${snapshot.selectedName ?? "none"}`);
|
|
491
612
|
console.log(`codex_auth=${snapshot.codexAuthAccountId ?? "none"}`);
|
|
613
|
+
console.log(`relay_slots=${snapshot.relaySlots.length}`);
|
|
492
614
|
}
|
package/dist/status.js
CHANGED
|
@@ -4,6 +4,8 @@ exports.collectAccountStatuses = collectAccountStatuses;
|
|
|
4
4
|
exports.summarizeAccountStatuses = summarizeAccountStatuses;
|
|
5
5
|
exports.renderStatusTable = renderStatusTable;
|
|
6
6
|
exports.renderStatusDetails = renderStatusDetails;
|
|
7
|
+
exports.renderRelayStatusTable = renderRelayStatusTable;
|
|
8
|
+
exports.renderRelayStatusDetails = renderRelayStatusDetails;
|
|
7
9
|
const config_1 = require("./config");
|
|
8
10
|
const account_store_1 = require("./account-store");
|
|
9
11
|
const state_1 = require("./state");
|
|
@@ -554,3 +556,73 @@ function renderStatusDetails(item, options) {
|
|
|
554
556
|
}
|
|
555
557
|
return lines.join("\n");
|
|
556
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* 将 relay slot 状态渲染为适合终端输出的表格文本。
|
|
561
|
+
*
|
|
562
|
+
* @param slots 待展示的 relay slot 列表。
|
|
563
|
+
* @param options 渲染选项;交互模式下可传入选择列配置。
|
|
564
|
+
* @returns 可直接打印到终端的表格字符串。
|
|
565
|
+
* @throws 无显式抛出。
|
|
566
|
+
*/
|
|
567
|
+
function renderRelayStatusTable(slots, options) {
|
|
568
|
+
const selectorColumn = options?.selectorColumn;
|
|
569
|
+
const compact = options?.compact ?? false;
|
|
570
|
+
const maxWidth = options?.maxWidth ?? Number.POSITIVE_INFINITY;
|
|
571
|
+
const compactHeader = maxWidth < 68;
|
|
572
|
+
const relayHeader = compactHeader ? "ID" : "RELAY";
|
|
573
|
+
const statusHeader = compactHeader ? "ST" : "STATUS";
|
|
574
|
+
const relayWidth = Math.max(getDisplayWidth(relayHeader), ...slots.map((item) => getDisplayWidth(item.name)));
|
|
575
|
+
const statusWidth = compactHeader ? 8 : 10;
|
|
576
|
+
const fixedWidth = (selectorColumn ? 4 + 2 : 0) + relayWidth + 2 + statusWidth + 2;
|
|
577
|
+
const baseUrlWidth = Number.isFinite(maxWidth)
|
|
578
|
+
? Math.max(12, Math.floor(maxWidth) - fixedWidth)
|
|
579
|
+
: Math.max(getDisplayWidth("BASE_URL"), ...slots.map((item) => getDisplayWidth(item.base_url)));
|
|
580
|
+
const rows = [
|
|
581
|
+
[
|
|
582
|
+
...(selectorColumn ? [" "] : []),
|
|
583
|
+
relayHeader,
|
|
584
|
+
statusHeader,
|
|
585
|
+
"BASE_URL"
|
|
586
|
+
]
|
|
587
|
+
];
|
|
588
|
+
for (const slot of slots) {
|
|
589
|
+
const selectorCell = selectorColumn
|
|
590
|
+
? `${selectorColumn.cursorRelayId === slot.id ? ">" : " "}[${selectorColumn.enabledById[slot.id] ? "x" : " "}]`
|
|
591
|
+
: null;
|
|
592
|
+
const status = slot.enabled ? "enabled" : "disabled";
|
|
593
|
+
rows.push([
|
|
594
|
+
...(selectorCell ? [selectorCell] : []),
|
|
595
|
+
styleNameCell(truncateCell(slot.name, relayWidth), options?.styled ?? false),
|
|
596
|
+
truncateCell(status, statusWidth),
|
|
597
|
+
truncateCell(slot.base_url, baseUrlWidth)
|
|
598
|
+
]);
|
|
599
|
+
}
|
|
600
|
+
const widths = rows[0].map((_, columnIndex) => Math.max(...rows.map((row) => getDisplayWidth(row[columnIndex]))));
|
|
601
|
+
return rows
|
|
602
|
+
.map((row) => row.map((cell, index) => padCell(cell, widths[index])).join(" "))
|
|
603
|
+
.join("\n");
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* 将当前选中 relay slot 渲染为紧凑详情区。
|
|
607
|
+
*
|
|
608
|
+
* @param slot 当前选中的 relay slot;为空时返回占位提示。
|
|
609
|
+
* @param options 详情区渲染选项。
|
|
610
|
+
* @returns 适合直接打印的详情区文本。
|
|
611
|
+
* @throws 无显式抛出。
|
|
612
|
+
*/
|
|
613
|
+
function renderRelayStatusDetails(slot, options) {
|
|
614
|
+
const includeHeader = options?.header ?? true;
|
|
615
|
+
if (!slot) {
|
|
616
|
+
return [includeHeader ? "[ relay ]" : "slot -", includeHeader ? "slot -" : ""]
|
|
617
|
+
.filter((line) => line.length > 0)
|
|
618
|
+
.join("\n");
|
|
619
|
+
}
|
|
620
|
+
const maxWidth = options?.maxWidth ?? Number.POSITIVE_INFINITY;
|
|
621
|
+
return [
|
|
622
|
+
...(includeHeader ? ["[ relay ]"] : []),
|
|
623
|
+
formatDetailLine("slot", slot.name, maxWidth),
|
|
624
|
+
formatDetailLine("status", slot.enabled ? "enabled" : "disabled", maxWidth),
|
|
625
|
+
formatDetailLine("base", slot.base_url, maxWidth),
|
|
626
|
+
formatDetailLine("key", "********", maxWidth)
|
|
627
|
+
].join("\n");
|
|
628
|
+
}
|