codex-slot 0.1.30 → 0.1.32

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/dist/cli.js CHANGED
@@ -10,37 +10,22 @@ const service_control_1 = require("./service-control");
10
10
  const status_command_1 = require("./status-command");
11
11
  const text_1 = require("./text");
12
12
  /**
13
- * 生成根 help 中的 relay 与模型出口命令说明。
13
+ * 生成根 help relay 与模型出口相关的示例命令。
14
14
  *
15
15
  * 业务含义:
16
- * 1. Commander 默认命令列表会按终端宽度折行,中英混排时可读性较差。
17
- * 2. 最近新增的 relay/use/current 命令需要用固定双语格式补充业务含义和使用方式。
16
+ * 1. 根 help 的主命令列表保持 Commander 默认风格。
17
+ * 2. relay/use/current 的常见用法只补充到示例区,避免额外文档块打断整体格式。
18
18
  *
19
19
  * @returns 可追加到根 help 的多行说明。
20
20
  * @throws 无显式抛出。
21
21
  */
22
- function renderRelayCommandHelp() {
22
+ function renderRelayCommandExamples() {
23
23
  return [
24
- "",
25
- "中转命令 / Relay commands:",
26
- " cslot relay add <name> --base-url <url> --api-key <key>",
27
- " 中文: 新增 OpenAI-compatible 中转槽位。",
28
- " English: Add an OpenAI-compatible relay slot.",
24
+ " cslot relay add third --base-url https://relay.example.com/v1 --api-key <key>",
29
25
  " cslot relay list",
30
- " 中文: 查看全部中转槽位,API key 会脱敏。",
31
- " English: List relay slots with API keys masked.",
32
- " cslot relay enable <name> / cslot relay disable <name>",
33
- " 中文: 控制某个中转槽位是否参与模型出口选择。",
34
- " English: Enable or disable a relay slot for model routing.",
35
- " cslot use relay <name>",
36
- " 中文: 固定模型请求走指定中转槽位。",
37
- " English: Route model requests through the selected relay slot.",
26
+ " cslot use relay third",
38
27
  " cslot use auth",
39
- " 中文: 恢复使用官方 Codex 账号池。",
40
- " English: Restore routing through the official Codex auth pool.",
41
- " cslot current",
42
- " 中文: 查看当前模型出口和 Codex App 登录态选择。",
43
- " English: Show the active model route and Codex App auth selection."
28
+ " cslot current"
44
29
  ];
45
30
  }
46
31
  /**
@@ -63,7 +48,7 @@ function configureRootProgram(program) {
63
48
  " cslot rename work work-main",
64
49
  " cslot start --port 4399",
65
50
  " cslot status --no-interactive",
66
- ...renderRelayCommandHelp(),
51
+ ...renderRelayCommandExamples(),
67
52
  "",
68
53
  `${(0, text_1.bi)("说明", "Notes")}:`,
69
54
  ` ${(0, text_1.bi)("`import current ~` 里的 current 只是示例槽位名,不是内置账号或工作空间。", "`current` in `import current ~` is only an example slot name, not a built-in account or workspace.")}`
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.renderInteractiveHelpLines = renderInteractiveHelpLines;
7
+ exports.renderInteractiveStatusLayout = renderInteractiveStatusLayout;
7
8
  exports.handleStatus = handleStatus;
8
9
  const node_readline_1 = __importDefault(require("node:readline"));
9
10
  const account_store_1 = require("./account-store");
@@ -218,18 +219,64 @@ function truncateVisible(value, maxWidth) {
218
219
  * @param leftLines 左栏文本行。
219
220
  * @param rightLines 右栏文本行。
220
221
  * @param gap 两栏之间的空格数量。
222
+ * @param leftWidth 左栏固定显示宽度;未传入时按左栏最长行自适应。
221
223
  * @returns 合并后的双栏文本行。
222
224
  * @throws 无显式抛出。
223
225
  */
224
- function renderColumns(leftLines, rightLines, gap) {
225
- const leftWidth = Math.max(0, ...leftLines.map((line) => getDisplayWidth(line)));
226
+ function renderColumns(leftLines, rightLines, gap, leftWidth) {
227
+ const resolvedLeftWidth = leftWidth ?? Math.max(0, ...leftLines.map((line) => getDisplayWidth(line)));
226
228
  const rowCount = Math.max(leftLines.length, rightLines.length);
227
229
  const rows = [];
228
230
  for (let index = 0; index < rowCount; index += 1) {
229
- rows.push(`${padVisible(leftLines[index] ?? "", leftWidth)}${" ".repeat(gap)}${rightLines[index] ?? ""}`.trimEnd());
231
+ rows.push(`${padVisible(leftLines[index] ?? "", resolvedLeftWidth)}${" ".repeat(gap)}${rightLines[index] ?? ""}`.trimEnd());
230
232
  }
231
233
  return rows;
232
234
  }
235
+ /**
236
+ * 按终端高度裁剪交互式面板输出行,避免写到最后一行后继续换行触发滚屏。
237
+ *
238
+ * 业务含义:
239
+ * 1. 交互面板每次都从左上角整屏重绘。
240
+ * 2. 当输出行数超过当前终端高度时,终端会滚动备用屏缓冲区,后续重绘会出现残影或错位。
241
+ * 3. 保留最后一行作为安全缓冲,兼容不同终端对末行写入与换行的处理差异。
242
+ *
243
+ * @param lines 已完成布局的面板行。
244
+ * @param screenHeight 当前终端行数;为空或非法时不裁剪。
245
+ * @returns 可安全输出到当前终端的面板行。
246
+ * @throws 无显式抛出。
247
+ */
248
+ function clipInteractiveLines(lines, screenHeight) {
249
+ if (screenHeight === undefined || !Number.isFinite(screenHeight) || screenHeight <= 1) {
250
+ return lines;
251
+ }
252
+ return lines.slice(0, Math.max(1, Math.floor(screenHeight) - 1));
253
+ }
254
+ /**
255
+ * 构建交互式状态面板的最终屏幕行。
256
+ *
257
+ * 业务含义:
258
+ * 1. 该方法只负责布局,不读取或写入 cslot 状态,便于用纯测试覆盖终端尺寸边界。
259
+ * 2. 宽屏时使用固定左栏宽度,让右侧详情栏在账号/relay 选择切换时保持稳定锚点。
260
+ * 3. 窄屏时改为上下布局,并按终端高度裁剪,避免上下移动触发重绘后滚屏。
261
+ *
262
+ * @param options 布局参数;`leftLines` 为账号与 relay 主列表,`sideLines` 为当前项、摘要与 help,`screenWidth`/`screenHeight` 来自当前终端尺寸,`styled` 控制分隔线样式。
263
+ * @returns 可直接传给终端输出函数的屏幕行。
264
+ * @throws 无显式抛出。
265
+ */
266
+ function renderInteractiveStatusLayout(options) {
267
+ const screenWidth = Math.max(1, Math.floor(options.screenWidth));
268
+ const wideLayout = screenWidth >= 104;
269
+ const leftWidth = wideLayout ? Math.max(68, Math.floor(screenWidth * 0.64)) : screenWidth;
270
+ const lines = wideLayout
271
+ ? renderColumns(options.leftLines, options.sideLines, 3, leftWidth)
272
+ : [
273
+ ...options.leftLines,
274
+ "",
275
+ renderDivider(screenWidth, options.styled),
276
+ ...options.sideLines
277
+ ];
278
+ return clipInteractiveLines(lines, options.screenHeight);
279
+ }
233
280
  /**
234
281
  * 进入交互式全屏缓冲区,并隐藏光标,确保后续重绘始终基于固定画布。
235
282
  *
@@ -261,7 +308,6 @@ function renderInteractiveScreen(lines) {
261
308
  node_readline_1.default.cursorTo(process.stdout, 0, 0);
262
309
  node_readline_1.default.clearScreenDown(process.stdout);
263
310
  process.stdout.write(lines.join("\n"));
264
- process.stdout.write("\n");
265
311
  }
266
312
  /**
267
313
  * 计算交互式状态面板的初始光标位置。
@@ -378,6 +424,7 @@ async function handleInteractiveToggle(initialStatuses) {
378
424
  let refreshStatusText = null;
379
425
  const render = () => {
380
426
  const screenWidth = process.stdout.columns ?? 80;
427
+ const screenHeight = process.stdout.rows;
381
428
  const styled = shouldUseAnsiStyle();
382
429
  const latestSnapshot = (0, status_service_1.getStatusSnapshot)();
383
430
  const items = buildInteractiveItems(accounts, relaySlots);
@@ -465,16 +512,13 @@ async function handleInteractiveToggle(initialStatuses) {
465
512
  "",
466
513
  ...relayLines
467
514
  ];
468
- if (wideLayout) {
469
- renderInteractiveScreen(renderColumns(leftLines, sideLines, 3));
470
- return;
471
- }
472
- renderInteractiveScreen([
473
- ...leftLines,
474
- "",
475
- renderDivider(screenWidth, styled),
476
- ...sideLines
477
- ]);
515
+ renderInteractiveScreen(renderInteractiveStatusLayout({
516
+ leftLines,
517
+ sideLines,
518
+ screenWidth,
519
+ screenHeight,
520
+ styled
521
+ }));
478
522
  };
479
523
  const applyChanges = () => {
480
524
  if (!accountChanged && !relayChanged) {
@@ -497,6 +541,7 @@ async function handleInteractiveToggle(initialStatuses) {
497
541
  closed = true;
498
542
  applyChanges();
499
543
  stdin.off("keypress", onKeypress);
544
+ process.stdout.off("resize", onResize);
500
545
  stdin.setRawMode?.(false);
501
546
  stdin.pause();
502
547
  leaveInteractiveScreen();
@@ -629,8 +674,14 @@ async function handleInteractiveToggle(initialStatuses) {
629
674
  exitInteractive();
630
675
  }
631
676
  };
677
+ const onResize = () => {
678
+ if (!closed) {
679
+ render();
680
+ }
681
+ };
632
682
  render();
633
683
  stdin.on("keypress", onKeypress);
684
+ process.stdout.on("resize", onResize);
634
685
  });
635
686
  }
636
687
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-slot",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "本地 Codex 多账号切换与状态管理工具",
5
5
  "type": "commonjs",
6
6
  "main": "dist/cli.js",