green-screen-proxy 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.
Files changed (67) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +32 -0
  3. package/dist/hp6530/connection.d.ts +51 -0
  4. package/dist/hp6530/connection.js +258 -0
  5. package/dist/hp6530/constants.d.ts +64 -0
  6. package/dist/hp6530/constants.js +135 -0
  7. package/dist/hp6530/encoder.d.ts +37 -0
  8. package/dist/hp6530/encoder.js +89 -0
  9. package/dist/hp6530/parser.d.ts +45 -0
  10. package/dist/hp6530/parser.js +255 -0
  11. package/dist/hp6530/screen.d.ts +104 -0
  12. package/dist/hp6530/screen.js +252 -0
  13. package/dist/mock/mock-routes.d.ts +2 -0
  14. package/dist/mock/mock-routes.js +231 -0
  15. package/dist/protocols/hp6530-handler.d.ts +29 -0
  16. package/dist/protocols/hp6530-handler.js +64 -0
  17. package/dist/protocols/index.d.ts +11 -0
  18. package/dist/protocols/index.js +27 -0
  19. package/dist/protocols/tn3270-handler.d.ts +26 -0
  20. package/dist/protocols/tn3270-handler.js +61 -0
  21. package/dist/protocols/tn5250-handler.d.ts +26 -0
  22. package/dist/protocols/tn5250-handler.js +62 -0
  23. package/dist/protocols/types.d.ts +59 -0
  24. package/dist/protocols/types.js +7 -0
  25. package/dist/protocols/vt-handler.d.ts +30 -0
  26. package/dist/protocols/vt-handler.js +67 -0
  27. package/dist/routes.d.ts +2 -0
  28. package/dist/routes.js +141 -0
  29. package/dist/server.d.ts +1 -0
  30. package/dist/server.js +34 -0
  31. package/dist/session.d.ts +32 -0
  32. package/dist/session.js +88 -0
  33. package/dist/tn3270/connection.d.ts +31 -0
  34. package/dist/tn3270/connection.js +266 -0
  35. package/dist/tn3270/constants.d.ts +262 -0
  36. package/dist/tn3270/constants.js +261 -0
  37. package/dist/tn3270/encoder.d.ts +24 -0
  38. package/dist/tn3270/encoder.js +97 -0
  39. package/dist/tn3270/parser.d.ts +22 -0
  40. package/dist/tn3270/parser.js +284 -0
  41. package/dist/tn3270/screen.d.ts +89 -0
  42. package/dist/tn3270/screen.js +207 -0
  43. package/dist/tn5250/connection.d.ts +41 -0
  44. package/dist/tn5250/connection.js +254 -0
  45. package/dist/tn5250/constants.d.ts +128 -0
  46. package/dist/tn5250/constants.js +156 -0
  47. package/dist/tn5250/ebcdic.d.ts +10 -0
  48. package/dist/tn5250/ebcdic.js +89 -0
  49. package/dist/tn5250/encoder.d.ts +30 -0
  50. package/dist/tn5250/encoder.js +121 -0
  51. package/dist/tn5250/parser.d.ts +33 -0
  52. package/dist/tn5250/parser.js +412 -0
  53. package/dist/tn5250/screen.d.ts +80 -0
  54. package/dist/tn5250/screen.js +155 -0
  55. package/dist/vt/connection.d.ts +45 -0
  56. package/dist/vt/connection.js +229 -0
  57. package/dist/vt/constants.d.ts +97 -0
  58. package/dist/vt/constants.js +163 -0
  59. package/dist/vt/encoder.d.ts +30 -0
  60. package/dist/vt/encoder.js +55 -0
  61. package/dist/vt/parser.d.ts +36 -0
  62. package/dist/vt/parser.js +534 -0
  63. package/dist/vt/screen.d.ts +101 -0
  64. package/dist/vt/screen.js +424 -0
  65. package/dist/websocket.d.ts +6 -0
  66. package/dist/websocket.js +50 -0
  67. package/package.json +57 -0
@@ -0,0 +1,30 @@
1
+ import { VTScreenBuffer } from './screen.js';
2
+ /**
3
+ * Encodes client input into VT terminal wire-format data.
4
+ *
5
+ * VT terminals are character-at-a-time (stream mode): each keystroke
6
+ * is sent immediately to the host. No block-mode buffering.
7
+ */
8
+ export declare class VTEncoder {
9
+ private screen;
10
+ /** Whether BACKSPACE sends DEL (0x7F) or BS (0x08). Default: DEL */
11
+ backspaceIsDel: boolean;
12
+ /** Whether ENTER sends CR (0x0D) or CRLF. Default: CR only */
13
+ enterIsCR: boolean;
14
+ constructor(screen: VTScreenBuffer);
15
+ /**
16
+ * Encode a key name into the VT escape sequence to send over the wire.
17
+ * Returns null if the key is unknown.
18
+ */
19
+ encodeKey(keyName: string): Buffer | null;
20
+ /**
21
+ * Encode plain text for sending to the host.
22
+ * Each character is sent as its ASCII byte.
23
+ */
24
+ encodeText(text: string): Buffer;
25
+ /**
26
+ * Insert text at the current cursor position (local echo + wire output).
27
+ * Returns true if text was encoded successfully.
28
+ */
29
+ insertText(text: string): boolean;
30
+ }
@@ -0,0 +1,55 @@
1
+ import { VT_KEYS } from './constants.js';
2
+ /**
3
+ * Encodes client input into VT terminal wire-format data.
4
+ *
5
+ * VT terminals are character-at-a-time (stream mode): each keystroke
6
+ * is sent immediately to the host. No block-mode buffering.
7
+ */
8
+ export class VTEncoder {
9
+ screen;
10
+ /** Whether BACKSPACE sends DEL (0x7F) or BS (0x08). Default: DEL */
11
+ backspaceIsDel = true;
12
+ /** Whether ENTER sends CR (0x0D) or CRLF. Default: CR only */
13
+ enterIsCR = true;
14
+ constructor(screen) {
15
+ this.screen = screen;
16
+ }
17
+ /**
18
+ * Encode a key name into the VT escape sequence to send over the wire.
19
+ * Returns null if the key is unknown.
20
+ */
21
+ encodeKey(keyName) {
22
+ const upper = keyName.toUpperCase();
23
+ const seq = VT_KEYS[upper];
24
+ if (seq) {
25
+ return Buffer.from(seq, 'binary');
26
+ }
27
+ // Ctrl+letter (Ctrl+C = 0x03, Ctrl+A = 0x01, etc.)
28
+ if (upper.startsWith('CTRL+') && upper.length === 6) {
29
+ const letter = upper.charAt(5);
30
+ const code = letter.charCodeAt(0) - 0x40; // A=1, B=2, ...
31
+ if (code >= 1 && code <= 26) {
32
+ return Buffer.from([code]);
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * Encode plain text for sending to the host.
39
+ * Each character is sent as its ASCII byte.
40
+ */
41
+ encodeText(text) {
42
+ return Buffer.from(text, 'utf-8');
43
+ }
44
+ /**
45
+ * Insert text at the current cursor position (local echo + wire output).
46
+ * Returns true if text was encoded successfully.
47
+ */
48
+ insertText(text) {
49
+ if (!text)
50
+ return false;
51
+ // VT is stream mode — no local screen buffering needed.
52
+ // The host will echo characters back and the parser will render them.
53
+ return true;
54
+ }
55
+ }
@@ -0,0 +1,36 @@
1
+ import { VTScreenBuffer } from './screen.js';
2
+ /**
3
+ * VT100/VT220/VT320 escape sequence parser.
4
+ *
5
+ * Processes a stream of bytes from the host, interpreting control characters
6
+ * and ANSI/VT escape sequences, and updating the screen buffer accordingly.
7
+ */
8
+ export declare class VTParser {
9
+ private screen;
10
+ private state;
11
+ /** Accumulated CSI parameter string */
12
+ private params;
13
+ /** CSI intermediate bytes */
14
+ private intermediates;
15
+ /** Whether the CSI sequence has a '?' prefix (DEC private mode) */
16
+ private decPrivate;
17
+ /** OSC accumulator */
18
+ private oscData;
19
+ constructor(screen: VTScreenBuffer);
20
+ /**
21
+ * Feed a chunk of data from the host into the parser.
22
+ * Returns true if the screen was modified.
23
+ */
24
+ feed(data: Buffer): boolean;
25
+ private handleNormal;
26
+ private handleControlChar;
27
+ private handleEsc;
28
+ private handleCsi;
29
+ /** Parse CSI parameters as an array of numbers (default values handled per command) */
30
+ private parseParams;
31
+ private executeCsi;
32
+ /** Handle DEC private mode sequences (CSI ? ... h/l) */
33
+ private executeDecPrivate;
34
+ private executeSgr;
35
+ private handleOsc;
36
+ }
@@ -0,0 +1,534 @@
1
+ import { defaultAttrs } from './screen.js';
2
+ import { ESC, CSI_CHAR, BS, HT, LF, VT, FF, CR, SO, SI, BEL, SGR } from './constants.js';
3
+ /**
4
+ * VT100/VT220/VT320 escape sequence parser.
5
+ *
6
+ * Processes a stream of bytes from the host, interpreting control characters
7
+ * and ANSI/VT escape sequences, and updating the screen buffer accordingly.
8
+ */
9
+ export class VTParser {
10
+ screen;
11
+ state = 0 /* State.NORMAL */;
12
+ /** Accumulated CSI parameter string */
13
+ params = '';
14
+ /** CSI intermediate bytes */
15
+ intermediates = '';
16
+ /** Whether the CSI sequence has a '?' prefix (DEC private mode) */
17
+ decPrivate = false;
18
+ /** OSC accumulator */
19
+ oscData = '';
20
+ constructor(screen) {
21
+ this.screen = screen;
22
+ }
23
+ /**
24
+ * Feed a chunk of data from the host into the parser.
25
+ * Returns true if the screen was modified.
26
+ */
27
+ feed(data) {
28
+ let modified = false;
29
+ for (let i = 0; i < data.length; i++) {
30
+ const byte = data[i];
31
+ switch (this.state) {
32
+ case 0 /* State.NORMAL */:
33
+ modified = this.handleNormal(byte) || modified;
34
+ break;
35
+ case 1 /* State.ESC */:
36
+ modified = this.handleEsc(byte) || modified;
37
+ break;
38
+ case 2 /* State.CSI_PARAM */:
39
+ case 3 /* State.CSI_INTER */:
40
+ modified = this.handleCsi(byte) || modified;
41
+ break;
42
+ case 4 /* State.OSC */:
43
+ this.handleOsc(byte);
44
+ break;
45
+ case 5 /* State.DCS */:
46
+ // Consume until ST (ESC \) — simplified: just wait for ESC or BEL
47
+ if (byte === ESC || byte === BEL) {
48
+ this.state = 0 /* State.NORMAL */;
49
+ }
50
+ break;
51
+ case 7 /* State.SS3 */:
52
+ // SS3 sequences: ESC O <final> — used for F1-F4, keypad
53
+ // From the host side these are rare; just consume and ignore
54
+ this.state = 0 /* State.NORMAL */;
55
+ break;
56
+ case 6 /* State.SS2 */:
57
+ this.state = 0 /* State.NORMAL */;
58
+ break;
59
+ }
60
+ }
61
+ return modified;
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // State: NORMAL
65
+ // ---------------------------------------------------------------------------
66
+ handleNormal(byte) {
67
+ // Control characters
68
+ if (byte === ESC) {
69
+ this.state = 1 /* State.ESC */;
70
+ return false;
71
+ }
72
+ if (byte < 0x20 || byte === 0x7f) {
73
+ return this.handleControlChar(byte);
74
+ }
75
+ // Printable character — write to screen
76
+ this.screen.writeChar(String.fromCharCode(byte));
77
+ return true;
78
+ }
79
+ handleControlChar(byte) {
80
+ switch (byte) {
81
+ case BS: // Backspace
82
+ if (this.screen.cursorCol > 0) {
83
+ this.screen.cursorCol--;
84
+ this.screen.pendingWrap = false;
85
+ }
86
+ return false;
87
+ case HT: // Horizontal tab
88
+ this.screen.tabForward();
89
+ return false;
90
+ case LF: // Line feed
91
+ case VT: // Vertical tab (treated as LF)
92
+ case FF: // Form feed (treated as LF)
93
+ this.screen.lineFeed();
94
+ return true;
95
+ case CR: // Carriage return
96
+ this.screen.cursorCol = 0;
97
+ this.screen.pendingWrap = false;
98
+ return false;
99
+ case SO: // Shift Out — select G1 charset (simplified: ignore)
100
+ return false;
101
+ case SI: // Shift In — select G0 charset (simplified: ignore)
102
+ return false;
103
+ case BEL: // Bell
104
+ return false;
105
+ default:
106
+ return false;
107
+ }
108
+ }
109
+ // ---------------------------------------------------------------------------
110
+ // State: ESC (received ESC byte)
111
+ // ---------------------------------------------------------------------------
112
+ handleEsc(byte) {
113
+ switch (byte) {
114
+ case CSI_CHAR: // '[' — Control Sequence Introducer
115
+ this.state = 2 /* State.CSI_PARAM */;
116
+ this.params = '';
117
+ this.intermediates = '';
118
+ this.decPrivate = false;
119
+ return false;
120
+ case 0x5d: // ']' — OSC (Operating System Command)
121
+ this.state = 4 /* State.OSC */;
122
+ this.oscData = '';
123
+ return false;
124
+ case 0x50: // 'P' — DCS (Device Control String)
125
+ this.state = 5 /* State.DCS */;
126
+ return false;
127
+ case 0x4e: // 'N' — SS2 (Single Shift 2)
128
+ this.state = 6 /* State.SS2 */;
129
+ return false;
130
+ case 0x4f: // 'O' — SS3 (Single Shift 3)
131
+ this.state = 7 /* State.SS3 */;
132
+ return false;
133
+ case 0x37: // '7' — DECSC (Save Cursor)
134
+ this.screen.saveCursor();
135
+ this.state = 0 /* State.NORMAL */;
136
+ return false;
137
+ case 0x38: // '8' — DECRC (Restore Cursor)
138
+ this.screen.restoreCursor();
139
+ this.state = 0 /* State.NORMAL */;
140
+ return false;
141
+ case 0x44: // 'D' — IND (Index / line feed)
142
+ this.screen.lineFeed();
143
+ this.state = 0 /* State.NORMAL */;
144
+ return true;
145
+ case 0x4d: // 'M' — RI (Reverse Index / reverse line feed)
146
+ this.screen.reverseLineFeed();
147
+ this.state = 0 /* State.NORMAL */;
148
+ return true;
149
+ case 0x45: // 'E' — NEL (Next Line = CR + LF)
150
+ this.screen.cursorCol = 0;
151
+ this.screen.lineFeed();
152
+ this.state = 0 /* State.NORMAL */;
153
+ return true;
154
+ case 0x48: // 'H' — HTS (Horizontal Tab Set) — simplified: ignore
155
+ this.state = 0 /* State.NORMAL */;
156
+ return false;
157
+ case 0x63: // 'c' — RIS (Reset to Initial State)
158
+ this.screen.reset();
159
+ this.state = 0 /* State.NORMAL */;
160
+ return true;
161
+ case 0x5c: // '\' — ST (String Terminator) — end of DCS/OSC
162
+ this.state = 0 /* State.NORMAL */;
163
+ return false;
164
+ case 0x28: // '(' — Designate G0 character set — consume next byte
165
+ case 0x29: // ')' — Designate G1 character set — consume next byte
166
+ case 0x2a: // '*' — Designate G2 character set
167
+ case 0x2b: // '+' — Designate G3 character set
168
+ // Next byte is the charset designator (B, 0, etc.) — ignore it
169
+ // We stay in a pseudo-state; simplification: just consume one more byte
170
+ // by not changing state. Actually we need to consume the next byte.
171
+ // Use a trick: stay in ESC state for one more byte? No — just go NORMAL
172
+ // and accept that we may misinterpret one character. For correctness,
173
+ // we handle it by noting this is a 3-byte sequence.
174
+ this.state = 0 /* State.NORMAL */; // next byte will be consumed as normal char
175
+ // This is a slight inaccuracy but charset switching is rarely
176
+ // semantically important for screen scraping.
177
+ return false;
178
+ default:
179
+ // Unknown ESC sequence — return to NORMAL
180
+ this.state = 0 /* State.NORMAL */;
181
+ return false;
182
+ }
183
+ }
184
+ // ---------------------------------------------------------------------------
185
+ // State: CSI (parsing CSI parameters and executing)
186
+ // ---------------------------------------------------------------------------
187
+ handleCsi(byte) {
188
+ // Check for '?' prefix (DEC private mode)
189
+ if (byte === 0x3f && this.params === '' && !this.decPrivate) { // '?'
190
+ this.decPrivate = true;
191
+ return false;
192
+ }
193
+ // Parameter bytes: 0x30-0x3F (digits, semicolons, etc.)
194
+ if (byte >= 0x30 && byte <= 0x3f) {
195
+ this.params += String.fromCharCode(byte);
196
+ return false;
197
+ }
198
+ // Intermediate bytes: 0x20-0x2F
199
+ if (byte >= 0x20 && byte <= 0x2f) {
200
+ this.intermediates += String.fromCharCode(byte);
201
+ this.state = 3 /* State.CSI_INTER */;
202
+ return false;
203
+ }
204
+ // Final byte: 0x40-0x7E — execute the sequence
205
+ if (byte >= 0x40 && byte <= 0x7e) {
206
+ const result = this.executeCsi(byte);
207
+ this.state = 0 /* State.NORMAL */;
208
+ return result;
209
+ }
210
+ // Invalid byte — abort
211
+ this.state = 0 /* State.NORMAL */;
212
+ return false;
213
+ }
214
+ /** Parse CSI parameters as an array of numbers (default values handled per command) */
215
+ parseParams() {
216
+ if (this.params === '')
217
+ return [];
218
+ return this.params.split(';').map((s) => {
219
+ const n = parseInt(s, 10);
220
+ return isNaN(n) ? 0 : n;
221
+ });
222
+ }
223
+ executeCsi(finalByte) {
224
+ const params = this.parseParams();
225
+ const p1 = params[0] || 0;
226
+ const finalChar = String.fromCharCode(finalByte);
227
+ // Handle intermediate bytes (e.g., space for some sequences)
228
+ if (this.intermediates.length > 0) {
229
+ // CSI ? ... h/l with intermediates, or CSI ... SP q (cursor style), etc.
230
+ // Mostly safe to ignore for screen-scraping purposes.
231
+ return false;
232
+ }
233
+ if (this.decPrivate) {
234
+ return this.executeDecPrivate(finalChar, params);
235
+ }
236
+ switch (finalChar) {
237
+ // ------- Cursor movement -------
238
+ case 'A': // CUU — Cursor Up
239
+ this.screen.setCursor(this.screen.cursorRow - Math.max(p1, 1), this.screen.cursorCol);
240
+ return false;
241
+ case 'B': // CUD — Cursor Down
242
+ this.screen.setCursor(this.screen.cursorRow + Math.max(p1, 1), this.screen.cursorCol);
243
+ return false;
244
+ case 'C': // CUF — Cursor Forward
245
+ this.screen.setCursor(this.screen.cursorRow, this.screen.cursorCol + Math.max(p1, 1));
246
+ return false;
247
+ case 'D': // CUB — Cursor Backward
248
+ this.screen.setCursor(this.screen.cursorRow, this.screen.cursorCol - Math.max(p1, 1));
249
+ return false;
250
+ case 'E': // CNL — Cursor Next Line
251
+ this.screen.setCursor(this.screen.cursorRow + Math.max(p1, 1), 0);
252
+ return false;
253
+ case 'F': // CPL — Cursor Previous Line
254
+ this.screen.setCursor(this.screen.cursorRow - Math.max(p1, 1), 0);
255
+ return false;
256
+ case 'G': // CHA — Cursor Horizontal Absolute
257
+ this.screen.setCursor(this.screen.cursorRow, Math.max(p1, 1) - 1);
258
+ return false;
259
+ case 'H': // CUP — Cursor Position
260
+ case 'f': // HVP — Horizontal and Vertical Position (same as CUP)
261
+ {
262
+ const row = (params[0] || 1) - 1;
263
+ const col = (params[1] || 1) - 1;
264
+ this.screen.setCursor(row, col);
265
+ return false;
266
+ }
267
+ case 'd': // VPA — Vertical Position Absolute
268
+ this.screen.setCursor(Math.max(p1, 1) - 1, this.screen.cursorCol);
269
+ return false;
270
+ // ------- Erase -------
271
+ case 'J': // ED — Erase in Display
272
+ this.screen.eraseInDisplay(p1);
273
+ return true;
274
+ case 'K': // EL — Erase in Line
275
+ this.screen.eraseInLine(p1);
276
+ return true;
277
+ case 'X': // ECH — Erase Characters
278
+ this.screen.eraseCharacters(Math.max(p1, 1));
279
+ return true;
280
+ // ------- Insert / Delete -------
281
+ case 'L': // IL — Insert Lines
282
+ this.screen.insertLines(Math.max(p1, 1));
283
+ return true;
284
+ case 'M': // DL — Delete Lines
285
+ this.screen.deleteLines(Math.max(p1, 1));
286
+ return true;
287
+ case '@': // ICH — Insert Characters
288
+ this.screen.insertCharacters(Math.max(p1, 1));
289
+ return true;
290
+ case 'P': // DCH — Delete Characters
291
+ this.screen.deleteCharacters(Math.max(p1, 1));
292
+ return true;
293
+ // ------- Scrolling -------
294
+ case 'S': // SU — Scroll Up
295
+ this.screen.scrollUp(Math.max(p1, 1));
296
+ return true;
297
+ case 'T': // SD — Scroll Down
298
+ this.screen.scrollDown(Math.max(p1, 1));
299
+ return true;
300
+ // ------- Attributes -------
301
+ case 'm': // SGR — Select Graphic Rendition
302
+ this.executeSgr(params);
303
+ return false;
304
+ // ------- Scroll region -------
305
+ case 'r': // DECSTBM — Set Top and Bottom Margins
306
+ {
307
+ const top = (params[0] || 1) - 1;
308
+ const bottom = (params[1] || this.screen.rows) - 1;
309
+ this.screen.scrollTop = Math.max(0, Math.min(top, this.screen.rows - 1));
310
+ this.screen.scrollBottom = Math.max(0, Math.min(bottom, this.screen.rows - 1));
311
+ // CUP to home after DECSTBM
312
+ this.screen.setCursor(0, 0);
313
+ return false;
314
+ }
315
+ // ------- Mode set/reset -------
316
+ case 'h': // SM — Set Mode
317
+ // Standard modes (non-DEC private) — most are irrelevant for screen scraping
318
+ return false;
319
+ case 'l': // RM — Reset Mode
320
+ return false;
321
+ // ------- Device status -------
322
+ case 'n': // DSR — Device Status Report
323
+ // 6 = CPR (Cursor Position Report) — we'd need to send response
324
+ // For now, ignore
325
+ return false;
326
+ case 'c': // DA — Device Attributes
327
+ // Ignore — we'd need to send a response
328
+ return false;
329
+ // ------- Tab -------
330
+ case 'I': // CHT — Cursor Horizontal Tab (forward n tabs)
331
+ {
332
+ const count = Math.max(p1, 1);
333
+ for (let t = 0; t < count; t++)
334
+ this.screen.tabForward();
335
+ return false;
336
+ }
337
+ case 'g': // TBC — Tab Clear — ignore
338
+ return false;
339
+ default:
340
+ // Unknown CSI sequence — ignore
341
+ return false;
342
+ }
343
+ }
344
+ /** Handle DEC private mode sequences (CSI ? ... h/l) */
345
+ executeDecPrivate(finalChar, params) {
346
+ const mode = params[0] || 0;
347
+ switch (finalChar) {
348
+ case 'h': // DECSET — Set DEC private mode
349
+ switch (mode) {
350
+ case 1: // DECCKM — Application cursor keys (affects what arrow keys send)
351
+ // Tracked but no screen effect
352
+ return false;
353
+ case 6: // DECOM — Origin mode
354
+ this.screen.originMode = true;
355
+ this.screen.setCursor(0, 0);
356
+ return false;
357
+ case 7: // DECAWM — Auto wrap mode
358
+ this.screen.autoWrap = true;
359
+ return false;
360
+ case 25: // DECTCEM — Show cursor (no visual effect in our buffer)
361
+ return false;
362
+ case 1049: // Save cursor + switch to alternate screen buffer
363
+ this.screen.saveCursor();
364
+ this.screen.eraseInDisplay(2);
365
+ return true;
366
+ case 47:
367
+ case 1047: // Alternate screen buffer
368
+ this.screen.eraseInDisplay(2);
369
+ return true;
370
+ }
371
+ return false;
372
+ case 'l': // DECRST — Reset DEC private mode
373
+ switch (mode) {
374
+ case 1: // DECCKM — Normal cursor keys
375
+ return false;
376
+ case 6: // DECOM — Origin mode off
377
+ this.screen.originMode = false;
378
+ this.screen.setCursor(0, 0);
379
+ return false;
380
+ case 7: // DECAWM — Auto wrap off
381
+ this.screen.autoWrap = false;
382
+ return false;
383
+ case 25: // DECTCEM — Hide cursor
384
+ return false;
385
+ case 1049: // Restore cursor + switch from alternate screen
386
+ this.screen.restoreCursor();
387
+ return true;
388
+ case 47:
389
+ case 1047:
390
+ return true;
391
+ }
392
+ return false;
393
+ default:
394
+ return false;
395
+ }
396
+ }
397
+ // ---------------------------------------------------------------------------
398
+ // SGR — Select Graphic Rendition
399
+ // ---------------------------------------------------------------------------
400
+ executeSgr(params) {
401
+ // If no params, treat as reset
402
+ if (params.length === 0)
403
+ params = [0];
404
+ const attrs = this.screen.currentAttrs;
405
+ for (let i = 0; i < params.length; i++) {
406
+ const p = params[i];
407
+ switch (p) {
408
+ case SGR.RESET:
409
+ Object.assign(attrs, defaultAttrs());
410
+ break;
411
+ case SGR.BOLD:
412
+ attrs.bold = true;
413
+ break;
414
+ case SGR.DIM:
415
+ attrs.dim = true;
416
+ break;
417
+ case SGR.ITALIC:
418
+ attrs.italic = true;
419
+ break;
420
+ case SGR.UNDERLINE:
421
+ attrs.underline = true;
422
+ break;
423
+ case SGR.BLINK:
424
+ case SGR.RAPID_BLINK:
425
+ attrs.blink = true;
426
+ break;
427
+ case SGR.REVERSE:
428
+ attrs.reverse = true;
429
+ break;
430
+ case SGR.HIDDEN:
431
+ attrs.hidden = true;
432
+ break;
433
+ case SGR.STRIKETHROUGH:
434
+ attrs.strikethrough = true;
435
+ break;
436
+ case SGR.NORMAL_INTENSITY:
437
+ attrs.bold = false;
438
+ attrs.dim = false;
439
+ break;
440
+ case SGR.NO_ITALIC:
441
+ attrs.italic = false;
442
+ break;
443
+ case SGR.NO_UNDERLINE:
444
+ attrs.underline = false;
445
+ break;
446
+ case SGR.NO_BLINK:
447
+ attrs.blink = false;
448
+ break;
449
+ case SGR.NO_REVERSE:
450
+ attrs.reverse = false;
451
+ break;
452
+ case SGR.NO_HIDDEN:
453
+ attrs.hidden = false;
454
+ break;
455
+ case SGR.NO_STRIKETHROUGH:
456
+ attrs.strikethrough = false;
457
+ break;
458
+ default:
459
+ // Foreground colors
460
+ if (p >= 30 && p <= 37) {
461
+ attrs.fg = p - 30;
462
+ }
463
+ else if (p === 39) {
464
+ attrs.fg = 8; // default
465
+ }
466
+ // Background colors
467
+ else if (p >= 40 && p <= 47) {
468
+ attrs.bg = p - 40;
469
+ }
470
+ else if (p === 49) {
471
+ attrs.bg = 8; // default
472
+ }
473
+ // Bright foreground
474
+ else if (p >= 90 && p <= 97) {
475
+ attrs.fg = p - 90; // Map to 0-7 (bright handled by bold in classic VT)
476
+ attrs.bold = true;
477
+ }
478
+ // Bright background
479
+ else if (p >= 100 && p <= 107) {
480
+ attrs.bg = p - 100;
481
+ }
482
+ // 256-color / truecolor: CSI 38;5;n m or CSI 38;2;r;g;b m
483
+ else if (p === 38) {
484
+ if (i + 1 < params.length && params[i + 1] === 5) {
485
+ // 256-color foreground — map to basic 8 if possible
486
+ if (i + 2 < params.length) {
487
+ const color = params[i + 2];
488
+ attrs.fg = color < 8 ? color : 8;
489
+ i += 2;
490
+ }
491
+ }
492
+ else if (i + 1 < params.length && params[i + 1] === 2) {
493
+ // Truecolor — skip r,g,b
494
+ i += 4;
495
+ attrs.fg = 8;
496
+ }
497
+ }
498
+ else if (p === 48) {
499
+ if (i + 1 < params.length && params[i + 1] === 5) {
500
+ if (i + 2 < params.length) {
501
+ const color = params[i + 2];
502
+ attrs.bg = color < 8 ? color : 8;
503
+ i += 2;
504
+ }
505
+ }
506
+ else if (i + 1 < params.length && params[i + 1] === 2) {
507
+ i += 4;
508
+ attrs.bg = 8;
509
+ }
510
+ }
511
+ break;
512
+ }
513
+ }
514
+ }
515
+ // ---------------------------------------------------------------------------
516
+ // State: OSC
517
+ // ---------------------------------------------------------------------------
518
+ handleOsc(byte) {
519
+ // OSC terminated by BEL (0x07) or ST (ESC \)
520
+ if (byte === BEL) {
521
+ // OSC complete — we ignore OSC data (window titles, etc.)
522
+ this.state = 0 /* State.NORMAL */;
523
+ return;
524
+ }
525
+ if (byte === ESC) {
526
+ // Could be start of ST (ESC \) — next byte should be '\'
527
+ // Simplified: just go to NORMAL; the '\' will be consumed harmlessly
528
+ this.state = 0 /* State.NORMAL */;
529
+ return;
530
+ }
531
+ // Accumulate (ignored but consumed)
532
+ this.oscData += String.fromCharCode(byte);
533
+ }
534
+ }