@anusornneal/chat-relay 0.11.0 → 0.11.1

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.
Files changed (2) hide show
  1. package/cli/tui.mjs +14 -5
  2. package/package.json +1 -1
package/cli/tui.mjs CHANGED
@@ -3,6 +3,10 @@ import { humanizeToolCall } from "../agent/toolcall-summary.mjs";
3
3
  const ESC = "\x1b[";
4
4
  const LABEL_WIDTH = 12;
5
5
  const GAP = 3;
6
+ const MIN_FRAME_WIDTH = 60;
7
+ const MAX_FRAME_WIDTH = 179;
8
+ const FRAME_FIXED_ROWS = 11;
9
+ const FRAME_BOTTOM_MARGIN = 1;
6
10
 
7
11
  function clamp(value, min, max) {
8
12
  return Math.max(min, Math.min(max, value));
@@ -22,7 +26,7 @@ function field(label, value, width) {
22
26
  }
23
27
 
24
28
  export function formatTwoColumnHeader(state, width = 110) {
25
- const usable = clamp(Number(width) || 110, 72, 180);
29
+ const usable = clamp(Number(width) || 110, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
26
30
  const leftWidth = Math.floor((usable - GAP) / 2);
27
31
  const rightWidth = usable - GAP - leftWidth;
28
32
  const rows = [
@@ -117,7 +121,9 @@ export class RemoteTui {
117
121
 
118
122
  start() {
119
123
  if (this.closed) return;
120
- this.output.write(ESC + "?25l");
124
+ // Use the terminal's alternate screen so periodic redraws never accumulate
125
+ // in scrollback. Keep one column unused to avoid automatic line wrapping.
126
+ this.output.write(ESC + "?1049h" + ESC + "?25l" + ESC + "2J" + ESC + "H");
121
127
  this.render();
122
128
  this.timer = setInterval(() => this.render(), 1000);
123
129
  this.timer.unref?.();
@@ -128,7 +134,7 @@ export class RemoteTui {
128
134
  this.closed = true;
129
135
  if (this.timer) clearInterval(this.timer);
130
136
  this.timer = null;
131
- this.output.write(ESC + "?25h\n");
137
+ this.output.write(ESC + "?25h" + ESC + "?1049l");
132
138
  }
133
139
 
134
140
  handleMessage(message) {
@@ -178,14 +184,17 @@ export class RemoteTui {
178
184
 
179
185
  render() {
180
186
  if (this.closed) return;
181
- const width = clamp(Number(this.output.columns) || 110, 72, 180);
187
+ const terminalWidth = Number(this.output.columns) || 110;
188
+ const width = clamp(terminalWidth - 1, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
182
189
  const height = Math.max(16, Number(this.output.rows) || 30);
183
190
  const separator = "─".repeat(width);
184
191
  const header = formatTwoColumnHeader({
185
192
  ...this.state,
186
193
  uptimeMs: Date.now() - this.startedAt,
187
194
  }, width);
188
- const availableRows = Math.max(3, height - 10);
195
+ // Fixed frame content occupies 11 rows. Leave one spare row at the
196
+ // bottom so Windows Terminal never scrolls the screen while repainting.
197
+ const availableRows = Math.max(1, height - FRAME_FIXED_ROWS - FRAME_BOTTOM_MARGIN);
189
198
  const rows = this.transactions.slice(-availableRows).map((item) => formatTransactionRow(item, width));
190
199
  while (rows.length < Math.min(availableRows, 4)) rows.unshift("");
191
200
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anusornneal/chat-relay",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Zero-checkout remote MCP agent for ChatGPT with terminal, filesystem, process, multi-user, and multi-agent support.",
5
5
  "type": "module",
6
6
  "bin": {