@retrovm/terminal 0.2.0 → 0.3.0

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 CHANGED
@@ -11,6 +11,9 @@ Depends on [`@retrovm/color`](https://www.npmjs.com/package/@retrovm/color) (bun
11
11
  - **Named color shortcuts** — `term.red(...)`, `term.bgBlue(...)` etc., generated automatically from the `Color` registry
12
12
  - **Full cursor control** — move, save/restore, show/hide
13
13
  - **Screen management** — clear, alternate buffer, scroll region, auto-wrap
14
+ - **Buffered output** — coalesce thousands of small writes into a single `write()` call with `buffer()` / `flush()`
15
+ - **Synchronized output** — wrap redraws in `sync()` for tear-free animation via DEC mode 2026
16
+ - **Pixel framebuffer** — render a `Uint32Array` pixel buffer directly to the terminal using the half-block trick (`framebuffer()`)
14
17
  - **Sink-agnostic** — works with Bun's stdout/stderr writers, Node.js `Writable`s, xterm.js terminals, or any test double
15
18
  - **`NO_COLOR` support** — respects the [no-color.org](https://no-color.org/) convention out of the box
16
19
  - **TypeScript-first** — strict types with full autocompletion for all color methods
@@ -104,8 +107,15 @@ term.ink(new Color('#3399ff'), 'Color instance')
104
107
  term.ink('#aaaaaa') // set color only, write nothing
105
108
  ```
106
109
 
110
+ #### `resetText(fmt?, ...args)`
111
+ Resets all text attributes (color, background, bold, dim, italic, underline, …) without touching the cursor or alternate screen.
112
+
113
+ ```typescript
114
+ term.red('colored').resetText(' — plain again')
115
+ ```
116
+
107
117
  #### `reset(fmt?, ...args)`
108
- Resets **all** attributes (color, background, bold, etc.) and optionally writes text.
118
+ Full teardown: shows the cursor, exits the alternate screen buffer, and resets all text attributes. Use this as the last call in a TUI lifecycle.
109
119
 
110
120
  ```typescript
111
121
  term.red('warning').reset(' — back to normal')
@@ -225,6 +235,98 @@ term.scrollRegion(1, process.stdout.rows) // reset to full screen
225
235
 
226
236
  ---
227
237
 
238
+ ### Buffered output
239
+
240
+ #### `buffer()`
241
+ Enters buffered mode. All subsequent emissions accumulate in memory instead of being written immediately. Calling `buffer()` while already buffered is a no-op.
242
+
243
+ #### `flush()`
244
+ Commits the buffer in a single `write()` call and exits buffered mode. Calling `flush()` when not buffered is a no-op.
245
+
246
+ ```typescript
247
+ term.buffer()
248
+ for (let i = 0; i < 1000; i++) term.moveTo(i % 24 + 1, 1).print('row %d', i)
249
+ term.flush() // one syscall instead of thousands
250
+ ```
251
+
252
+ #### `raw(data)`
253
+ Writes a pre-built string directly, bypassing `util.format`. Useful in hot loops where the caller has already constructed the exact bytes to emit.
254
+
255
+ ```typescript
256
+ const seq = '\x1b[38;2;255;128;0m' // precomputed orange
257
+ term.raw(seq).raw('hot loop text').resetText()
258
+ ```
259
+
260
+ ---
261
+
262
+ ### Synchronized output
263
+
264
+ #### `sync(fn)`
265
+ Runs `fn` inside a synchronized-output block (DEC private mode 2026). The terminal accumulates all changes and presents them atomically when the block ends, eliminating tearing in TUIs and animations. Terminals that don't support mode 2026 ignore the escape silently.
266
+
267
+ Automatically enables buffered mode for the duration of the block. Re-entrant: nested `sync()` calls share the outermost block.
268
+
269
+ ```typescript
270
+ out.sync(() => {
271
+ for (let row = 0; row < H; row++) {
272
+ for (let col = 0; col < W; col++) {
273
+ out.moveTo(row + 1, col + 1).paper(bg).ink(fg).raw('▄')
274
+ }
275
+ }
276
+ })
277
+ ```
278
+
279
+ ---
280
+
281
+ ### Pixel graphics
282
+
283
+ #### `framebuffer(fb, w, h)`
284
+ Renders a raw pixel buffer to the terminal using the half-block trick. Each pair of vertically adjacent pixels is encoded as a single `▄` character: the upper pixel becomes the background color and the lower pixel the foreground color, doubling the effective vertical resolution.
285
+
286
+ - **`fb`** — `Uint32Array` of packed pixels. Format: little-endian RGBA (`0xAABBGGRR`), alpha ignored. Matches `Canvas.getImageData` output on little-endian systems.
287
+ - **`w`** — Width in pixels (terminal columns).
288
+ - **`h`** — Height in pixels. Must be even.
289
+
290
+ The entire frame is assembled into one string before writing; the write is wrapped in `sync()`.
291
+
292
+ ```typescript
293
+ const W = out.width ?? 80
294
+ const H = (out.height ?? 24) * 2 // 2 pixel rows per character row
295
+ const fb = new Uint32Array(W * H)
296
+
297
+ // fill fb with 0xAABBGGRR pixels...
298
+ fb[0] = 0xFF0000FF // red pixel at (0, 0)
299
+
300
+ out.framebuffer(fb, W, H)
301
+ ```
302
+
303
+ ---
304
+
305
+ ### Dimensions
306
+
307
+ #### `width` *(getter)*
308
+ Current width of the terminal in columns, as reported by the writer. Returns `-1` if the writer does not implement `width()`. Always reflects the current size — read on every access, so it stays correct after a resize.
309
+
310
+ #### `height` *(getter)*
311
+ Current height of the terminal in rows, as reported by the writer. Returns `-1` if the writer does not implement `height()`. Same live semantics as `width`.
312
+
313
+ The built-in `Terminal.out` and `Terminal.err` instances always provide both — wired to `process.stdout.columns` / `process.stdout.rows` in both Bun and Node.
314
+
315
+ Custom sinks can opt in by implementing the optional `width` and `height` functions on `ITerminalWriter`:
316
+
317
+ ```typescript
318
+ const term = createTerminal({
319
+ write: s => myCanvas.write(s),
320
+ width: () => myCanvas.cols,
321
+ height: () => myCanvas.rows,
322
+ })
323
+
324
+ term.width // myCanvas.cols, live
325
+ term.height // myCanvas.rows, live
326
+ ```
327
+
328
+ ---
329
+
228
330
  ### Options
229
331
 
230
332
  #### `plain`
@@ -346,6 +448,20 @@ Procedural fire simulation using the **half-block trick**: the `▄` character h
346
448
  bun run sample/fire.ts
347
449
  ```
348
450
 
451
+ ### `snake.ts`
452
+ Fully playable Snake game running in the alternate buffer. Uses `sync()` for tear-free rendering and updates only the cells that actually changed each tick. The snake body carries a smooth cyan-to-teal gradient; food pulses with an orange-yellow glow. Controls: WASD or arrow keys, Q to quit.
453
+
454
+ ```sh
455
+ bun run sample/snake.ts
456
+ ```
457
+
458
+ ### `rotozoom.ts`
459
+ Real-time rotating and zooming texture effect. Each frame the CPU computes a rotated/scaled UV map over the terminal dimensions and calls `out.framebuffer()` to push the result as a pixel buffer — demonstrating that `framebuffer()` is fast enough for 60 fps animation. Exit with any key.
460
+
461
+ ```sh
462
+ bun run sample/rotozoom.ts
463
+ ```
464
+
349
465
  ### `sysmon.ts`
350
466
  Live system monitor that refreshes every second inside the alternate buffer. Two-panel layout drawn with box-drawing characters (`┌┬┐│├┤└┴┘─`). Left panel shows overall CPU average and per-core usage bars; right panel shows system RAM and process heap. All bars are color-coded: green below 70%, orange 70–90%, red above 90%.
351
467
 
package/dist/node.js CHANGED
@@ -27,6 +27,11 @@ class Color {
27
27
  this._g = g;
28
28
  this._b = b;
29
29
  this._a = a;
30
+ } else if (typeof rs === "number" && g === undefined && b === undefined) {
31
+ this._r = (rs & 255) / 255;
32
+ this._g = (rs >> 8 & 255) / 255;
33
+ this._b = (rs >> 16 & 255) / 255;
34
+ this._a = (rs >> 24 & 255) / 255;
30
35
  } else if (typeof rs === "string") {
31
36
  const match = rs.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i);
32
37
  if (match) {
@@ -126,6 +131,9 @@ class Color {
126
131
  this._ansiFg = undefined;
127
132
  this._ansiBg = undefined;
128
133
  }
134
+ toRGBA() {
135
+ return (Math.round(this._a * 255) << 24 | Math.round(this._b * 255) << 16 | Math.round(this._g * 255) << 8 | Math.round(this._r * 255)) >>> 0;
136
+ }
129
137
  toString() {
130
138
  return `#${Math.round(this._r * 255).toString(16).padStart(2, "0")}${Math.round(this._g * 255).toString(16).padStart(2, "0")}${Math.round(this._b * 255).toString(16).padStart(2, "0")}${Math.round(this._a * 255).toString(16).padStart(2, "0")}`;
131
139
  }
@@ -461,6 +469,48 @@ class TerminalCore {
461
469
  else
462
470
  this.writer.write(data);
463
471
  }
472
+ framebuffer(fb, w, h) {
473
+ const ch = [];
474
+ for (let y = 0;y < h; y += 2) {
475
+ for (let x = 0;x < w; x++) {
476
+ const i = y * w + x;
477
+ const c1 = fb[i];
478
+ const c2 = fb[i + w];
479
+ const b1 = c1 >> 16 & 255;
480
+ const g1 = c1 >> 8 & 255;
481
+ const r1 = c1 & 255;
482
+ const b2 = c2 >> 16 & 255;
483
+ const g2 = c2 >> 8 & 255;
484
+ const r2 = c2 & 255;
485
+ ch.push("\x1B[48;2;");
486
+ ch.push(r1.toString());
487
+ ch.push(";");
488
+ ch.push(g1.toString());
489
+ ch.push(";");
490
+ ch.push(b1.toString());
491
+ ch.push("m\x1B[38;2;");
492
+ ch.push(r2.toString());
493
+ ch.push(";");
494
+ ch.push(g2.toString());
495
+ ch.push(";");
496
+ ch.push(b2.toString());
497
+ ch.push("m▄");
498
+ }
499
+ }
500
+ this.sync(() => {
501
+ this.raw(ch.join(""));
502
+ });
503
+ }
504
+ get width() {
505
+ if (this.writer.width)
506
+ return this.writer.width();
507
+ return -1;
508
+ }
509
+ get height() {
510
+ if (this.writer.height)
511
+ return this.writer.height();
512
+ return -1;
513
+ }
464
514
  }
465
515
  function installColorMethods() {
466
516
  const proto = TerminalCore.prototype;
@@ -484,8 +534,8 @@ function createTerminal(writer, options = {}) {
484
534
  }
485
535
  // src/node.ts
486
536
  var Terminal = {
487
- out: createTerminal(process.stdout),
488
- err: createTerminal(process.stderr)
537
+ out: createTerminal({ write: (data) => process.stdout.write(data), width: () => process.stdout.columns ?? 80, height: () => process.stdout.rows ?? 24 }),
538
+ err: createTerminal({ write: (data) => process.stderr.write(data), width: () => process.stderr.columns ?? 80, height: () => process.stderr.rows ?? 24 })
489
539
  };
490
540
  export {
491
541
  createTerminal,
@@ -6,6 +6,8 @@ import { Color } from '@retrovm/color';
6
6
  */
7
7
  export interface ITerminalWriter {
8
8
  write(data: string): void;
9
+ width?: () => number;
10
+ height?: () => number;
9
11
  }
10
12
  /**
11
13
  * Extracts from `Color` only the keys whose value is an instance of `Color`,
@@ -212,6 +214,47 @@ declare class TerminalCore {
212
214
  */
213
215
  sync(fn: () => void): this;
214
216
  private emit;
217
+ /**
218
+ * Renders a raw pixel buffer to the terminal using the half-block trick.
219
+ *
220
+ * Each pair of vertically adjacent pixels is encoded as a single `▄`
221
+ * character: the upper pixel becomes the background color and the lower
222
+ * pixel becomes the foreground color, effectively doubling the vertical
223
+ * resolution — one character row covers two pixel rows.
224
+ *
225
+ * Pixel format: little-endian RGBA packed into a `Uint32Array`. Each
226
+ * element is `0xAABBGGRR` — R in the low byte, G in the next, B in the
227
+ * next, alpha ignored. This matches the layout produced by
228
+ * `Canvas.getImageData` and most image-decoding libraries when read as a
229
+ * `Uint32Array` on a little-endian system.
230
+ *
231
+ * The entire frame is assembled into one string before writing, which is
232
+ * far cheaper than calling `ink`/`paper`/`raw` per pixel. The write is
233
+ * wrapped in `sync()` to reduce tearing on capable terminals.
234
+ *
235
+ * @param fb - Pixel buffer. Length must be at least `w * h`.
236
+ * @param w - Width in pixels (equals the number of terminal columns used).
237
+ * @param h - Height in pixels. Must be even; an odd last row is ignored.
238
+ */
239
+ framebuffer(fb: Uint32Array, w: number, h: number): void;
240
+ /**
241
+ * Current width of the terminal in columns, as reported by the writer.
242
+ * Returns `-1` if the writer does not implement `width()`.
243
+ *
244
+ * Called on every access so it always reflects the current size, even after
245
+ * a resize event. The built-in Bun and Node writers read
246
+ * `process.stdout.columns` each time.
247
+ */
248
+ get width(): number;
249
+ /**
250
+ * Current height of the terminal in rows, as reported by the writer.
251
+ * Returns `-1` if the writer does not implement `height()`.
252
+ *
253
+ * Called on every access so it always reflects the current size, even after
254
+ * a resize event. The built-in Bun and Node writers read
255
+ * `process.stdout.rows` each time.
256
+ */
257
+ get height(): number;
215
258
  }
216
259
  /**
217
260
  * Creates a new `Terminal` instance backed by `writer`.
package/dist/terminal.js CHANGED
@@ -27,6 +27,11 @@ class Color {
27
27
  this._g = g;
28
28
  this._b = b;
29
29
  this._a = a;
30
+ } else if (typeof rs === "number" && g === undefined && b === undefined) {
31
+ this._r = (rs & 255) / 255;
32
+ this._g = (rs >> 8 & 255) / 255;
33
+ this._b = (rs >> 16 & 255) / 255;
34
+ this._a = (rs >> 24 & 255) / 255;
30
35
  } else if (typeof rs === "string") {
31
36
  const match = rs.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i);
32
37
  if (match) {
@@ -126,6 +131,9 @@ class Color {
126
131
  this._ansiFg = undefined;
127
132
  this._ansiBg = undefined;
128
133
  }
134
+ toRGBA() {
135
+ return (Math.round(this._a * 255) << 24 | Math.round(this._b * 255) << 16 | Math.round(this._g * 255) << 8 | Math.round(this._r * 255)) >>> 0;
136
+ }
129
137
  toString() {
130
138
  return `#${Math.round(this._r * 255).toString(16).padStart(2, "0")}${Math.round(this._g * 255).toString(16).padStart(2, "0")}${Math.round(this._b * 255).toString(16).padStart(2, "0")}${Math.round(this._a * 255).toString(16).padStart(2, "0")}`;
131
139
  }
@@ -461,6 +469,48 @@ class TerminalCore {
461
469
  else
462
470
  this.writer.write(data);
463
471
  }
472
+ framebuffer(fb, w, h) {
473
+ const ch = [];
474
+ for (let y = 0;y < h; y += 2) {
475
+ for (let x = 0;x < w; x++) {
476
+ const i = y * w + x;
477
+ const c1 = fb[i];
478
+ const c2 = fb[i + w];
479
+ const b1 = c1 >> 16 & 255;
480
+ const g1 = c1 >> 8 & 255;
481
+ const r1 = c1 & 255;
482
+ const b2 = c2 >> 16 & 255;
483
+ const g2 = c2 >> 8 & 255;
484
+ const r2 = c2 & 255;
485
+ ch.push("\x1B[48;2;");
486
+ ch.push(r1.toString());
487
+ ch.push(";");
488
+ ch.push(g1.toString());
489
+ ch.push(";");
490
+ ch.push(b1.toString());
491
+ ch.push("m\x1B[38;2;");
492
+ ch.push(r2.toString());
493
+ ch.push(";");
494
+ ch.push(g2.toString());
495
+ ch.push(";");
496
+ ch.push(b2.toString());
497
+ ch.push("m▄");
498
+ }
499
+ }
500
+ this.sync(() => {
501
+ this.raw(ch.join(""));
502
+ });
503
+ }
504
+ get width() {
505
+ if (this.writer.width)
506
+ return this.writer.width();
507
+ return -1;
508
+ }
509
+ get height() {
510
+ if (this.writer.height)
511
+ return this.writer.height();
512
+ return -1;
513
+ }
464
514
  }
465
515
  function installColorMethods() {
466
516
  const proto = TerminalCore.prototype;
package/package.json CHANGED
@@ -1,28 +1,19 @@
1
1
  {
2
2
  "name": "@retrovm/terminal",
3
- "version": "0.2.0",
4
- "description": "Fluent ANSI terminal library — 24-bit color, cursor control and screen management for Bun and Node.js",
3
+ "version": "0.3.0",
5
4
  "author": "Juan Carlos González Amestoy",
6
- "license": "MIT",
7
- "keywords": [
8
- "terminal",
9
- "ansi",
10
- "color",
11
- "cursor",
12
- "tui",
13
- "cli",
14
- "bun",
15
- "fluent"
16
- ],
17
5
  "repository": {
18
6
  "type": "git",
19
7
  "url": "https://github.com/retrovm/terminal.git"
20
8
  },
21
- "type": "module",
22
9
  "module": "src/terminal.ts",
23
- "scripts": {
24
- "build": "bun build src/terminal.ts src/node.ts --outdir dist --target node && tsc -p tsconfig.build.json",
25
- "prepublishOnly": "bun run build"
10
+ "devDependencies": {
11
+ "@types/bun": "latest",
12
+ "@types/node": "^25.9.1",
13
+ "typescript": "^5.9.3"
14
+ },
15
+ "peerDependencies": {
16
+ "typescript": "^5"
26
17
  },
27
18
  "exports": {
28
19
  ".": {
@@ -35,22 +26,31 @@
35
26
  "import": "./dist/terminal.js"
36
27
  }
37
28
  },
29
+ "description": "Fluent ANSI terminal library — 24-bit color, cursor control and screen management for Bun and Node.js",
38
30
  "files": [
39
31
  "src",
40
32
  "dist"
41
33
  ],
42
- "devDependencies": {
43
- "@types/bun": "latest",
44
- "@types/node": "^25.8.0",
45
- "typescript": "^5"
34
+ "keywords": [
35
+ "terminal",
36
+ "ansi",
37
+ "color",
38
+ "cursor",
39
+ "tui",
40
+ "cli",
41
+ "bun",
42
+ "fluent"
43
+ ],
44
+ "license": "MIT",
45
+ "publishConfig": {
46
+ "access": "public"
46
47
  },
47
- "peerDependencies": {
48
- "typescript": "^5"
48
+ "scripts": {
49
+ "build": "bun build src/terminal.ts src/node.ts --outdir dist --target node && tsc -p tsconfig.build.json",
50
+ "prepublishOnly": "bun run build"
49
51
  },
52
+ "type": "module",
50
53
  "dependencies": {
51
- "@retrovm/color": "^0.1.1"
52
- },
53
- "publishConfig": {
54
- "access": "public"
54
+ "@retrovm/color": "^0.2.2"
55
55
  }
56
56
  }
package/src/bun.ts CHANGED
@@ -1,33 +1,33 @@
1
- /*Copyright (c) 2026 Juan Carlos González Amestoy
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy
4
- of this software and associated documentation files (the "Software"), to deal
5
- in the Software without restriction, including without limitation the rights
6
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- copies of the Software, and to permit persons to whom the Software is
8
- furnished to do so, subject to the following conditions:
9
-
10
- The above copyright notice and this permission notice shall be included in all
11
- copies or substantial portions of the Software.
12
-
13
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
- SOFTWARE.*/
20
-
21
- import { createTerminal } from "./terminal"
22
- export { createTerminal, type ITerminal, type ITerminalWriter } from "./terminal"
23
-
24
- const _outWriter = Bun.stdout.writer()
25
- const _errWriter = Bun.stderr.writer()
26
-
27
- const _writeOut = (data: string) => { _outWriter.write(data); _outWriter.flush() }
28
- const _writeErr = (data: string) => { _errWriter.write(data); _errWriter.flush() }
29
-
30
- export const Terminal = {
31
- out: createTerminal({ write: _writeOut }),
32
- err: createTerminal({ write: _writeErr }),
1
+ /*Copyright (c) 2026 Juan Carlos González Amestoy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.*/
20
+
21
+ import { createTerminal } from "./terminal"
22
+ export { createTerminal, type ITerminal, type ITerminalWriter } from "./terminal"
23
+
24
+ const _outWriter = Bun.stdout.writer()
25
+ const _errWriter = Bun.stderr.writer()
26
+
27
+ const _writeOut = (data: string) => { _outWriter.write(data); _outWriter.flush() }
28
+ const _writeErr = (data: string) => { _errWriter.write(data); _errWriter.flush() }
29
+
30
+ export const Terminal = {
31
+ out: createTerminal({ write: _writeOut, width: () => process.stdout.columns ?? 80, height: () => process.stdout.rows ?? 24 }),
32
+ err: createTerminal({ write: _writeErr, width: () => process.stderr.columns ?? 80, height: () => process.stderr.rows ?? 24 }),
33
33
  }
package/src/node.ts CHANGED
@@ -2,6 +2,6 @@ import { createTerminal } from "./terminal"
2
2
  export { createTerminal, type ITerminal, type ITerminalWriter } from "./terminal"
3
3
 
4
4
  export const Terminal = {
5
- out: createTerminal(process.stdout),
6
- err: createTerminal(process.stderr),
5
+ out: createTerminal({ write: (data: string) => process.stdout.write(data), width: () => process.stdout.columns ?? 80, height: () => process.stdout.rows ?? 24 }),
6
+ err: createTerminal({ write: (data: string) => process.stderr.write(data), width: () => process.stderr.columns ?? 80, height: () => process.stderr.rows ?? 24 }),
7
7
  }