codex-slot 0.1.31 → 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.
@@ -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.31",
3
+ "version": "0.1.32",
4
4
  "description": "本地 Codex 多账号切换与状态管理工具",
5
5
  "type": "commonjs",
6
6
  "main": "dist/cli.js",