synabun 1.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 (187) hide show
  1. package/CHANGELOG.md +665 -0
  2. package/LICENSE +191 -0
  3. package/README.md +853 -0
  4. package/hooks/claude-code/post-plan.mjs +391 -0
  5. package/hooks/claude-code/post-remember.mjs +239 -0
  6. package/hooks/claude-code/pre-compact.mjs +241 -0
  7. package/hooks/claude-code/pre-websearch.mjs +90 -0
  8. package/hooks/claude-code/prompt-submit.mjs +861 -0
  9. package/hooks/claude-code/session-start.mjs +494 -0
  10. package/hooks/claude-code/shared.mjs +368 -0
  11. package/hooks/claude-code/stop.mjs +646 -0
  12. package/hooks/claude-code/test-user-learning.mjs +531 -0
  13. package/mcp-server/package.json +38 -0
  14. package/mcp-server/run.mjs +10 -0
  15. package/mcp-server/src/config.ts +70 -0
  16. package/mcp-server/src/http.ts +93 -0
  17. package/mcp-server/src/index.ts +203 -0
  18. package/mcp-server/src/preload-http.ts +25 -0
  19. package/mcp-server/src/preload.ts +29 -0
  20. package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
  21. package/mcp-server/src/services/categories.ts +429 -0
  22. package/mcp-server/src/services/discord.ts +702 -0
  23. package/mcp-server/src/services/file-checksums.ts +33 -0
  24. package/mcp-server/src/services/local-embeddings.ts +77 -0
  25. package/mcp-server/src/services/neural-interface.ts +344 -0
  26. package/mcp-server/src/services/sqlite.ts +823 -0
  27. package/mcp-server/src/tools/browser-advanced.ts +123 -0
  28. package/mcp-server/src/tools/browser-interact.ts +255 -0
  29. package/mcp-server/src/tools/browser-navigate.ts +77 -0
  30. package/mcp-server/src/tools/browser-observe.ts +1330 -0
  31. package/mcp-server/src/tools/browser.ts +107 -0
  32. package/mcp-server/src/tools/card-tools.ts +193 -0
  33. package/mcp-server/src/tools/card.ts +22 -0
  34. package/mcp-server/src/tools/category.ts +379 -0
  35. package/mcp-server/src/tools/discord-channel.ts +274 -0
  36. package/mcp-server/src/tools/discord-guild.ts +188 -0
  37. package/mcp-server/src/tools/discord-member.ts +158 -0
  38. package/mcp-server/src/tools/discord-message.ts +231 -0
  39. package/mcp-server/src/tools/discord-onboarding.ts +228 -0
  40. package/mcp-server/src/tools/discord-role.ts +209 -0
  41. package/mcp-server/src/tools/discord-thread.ts +110 -0
  42. package/mcp-server/src/tools/discord-webhook.ts +176 -0
  43. package/mcp-server/src/tools/discord.ts +40 -0
  44. package/mcp-server/src/tools/forget.ts +38 -0
  45. package/mcp-server/src/tools/git-tools.ts +141 -0
  46. package/mcp-server/src/tools/git.ts +13 -0
  47. package/mcp-server/src/tools/image-tools.ts +98 -0
  48. package/mcp-server/src/tools/image.ts +17 -0
  49. package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
  50. package/mcp-server/src/tools/leonardo.ts +28 -0
  51. package/mcp-server/src/tools/loop.ts +348 -0
  52. package/mcp-server/src/tools/memories.ts +118 -0
  53. package/mcp-server/src/tools/recall.ts +323 -0
  54. package/mcp-server/src/tools/reflect.ts +145 -0
  55. package/mcp-server/src/tools/remember.ts +115 -0
  56. package/mcp-server/src/tools/response.ts +26 -0
  57. package/mcp-server/src/tools/restore.ts +34 -0
  58. package/mcp-server/src/tools/sync.ts +114 -0
  59. package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
  60. package/mcp-server/src/tools/tictactoe.ts +13 -0
  61. package/mcp-server/src/tools/utils.ts +25 -0
  62. package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
  63. package/mcp-server/src/tools/whiteboard.ts +22 -0
  64. package/mcp-server/src/tui.ts +940 -0
  65. package/mcp-server/src/types.ts +85 -0
  66. package/mcp-server/tsconfig.json +19 -0
  67. package/neural-interface/package.json +27 -0
  68. package/neural-interface/public/apple-touch-icon.png +0 -0
  69. package/neural-interface/public/category-logos/claude.svg +1 -0
  70. package/neural-interface/public/category-logos/gemini-color.svg +1 -0
  71. package/neural-interface/public/category-logos/openai.svg +1 -0
  72. package/neural-interface/public/category-logos/synabun.png +0 -0
  73. package/neural-interface/public/claude-chat.css +878 -0
  74. package/neural-interface/public/claude-chat.html +92 -0
  75. package/neural-interface/public/claude-chat.js +1168 -0
  76. package/neural-interface/public/favicon-16x16.png +0 -0
  77. package/neural-interface/public/favicon-32x32.png +0 -0
  78. package/neural-interface/public/gallery.svg +6 -0
  79. package/neural-interface/public/icon-192.png +0 -0
  80. package/neural-interface/public/icon-512.png +0 -0
  81. package/neural-interface/public/index-legacy.html +14677 -0
  82. package/neural-interface/public/index.html +212 -0
  83. package/neural-interface/public/index2d-legacy.html +8193 -0
  84. package/neural-interface/public/index2d.html +127 -0
  85. package/neural-interface/public/invite.html +351 -0
  86. package/neural-interface/public/leonardoai.svg +1 -0
  87. package/neural-interface/public/logo-synabun.png +0 -0
  88. package/neural-interface/public/logoHD.png +0 -0
  89. package/neural-interface/public/manifest.json +14 -0
  90. package/neural-interface/public/offline.html +299 -0
  91. package/neural-interface/public/onboarding.html +2335 -0
  92. package/neural-interface/public/openclaw-logo-text.png +0 -0
  93. package/neural-interface/public/pin-svgrepo-com.svg +25 -0
  94. package/neural-interface/public/sessionicon.svg +8 -0
  95. package/neural-interface/public/shared/ansi-parser.js +305 -0
  96. package/neural-interface/public/shared/api.js +1043 -0
  97. package/neural-interface/public/shared/colors.js +153 -0
  98. package/neural-interface/public/shared/constants.js +141 -0
  99. package/neural-interface/public/shared/html-shell.js +777 -0
  100. package/neural-interface/public/shared/html-term-renderer.js +1022 -0
  101. package/neural-interface/public/shared/i18n.js +147 -0
  102. package/neural-interface/public/shared/registry.js +82 -0
  103. package/neural-interface/public/shared/state.js +86 -0
  104. package/neural-interface/public/shared/storage.js +150 -0
  105. package/neural-interface/public/shared/styles.css +21212 -0
  106. package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
  107. package/neural-interface/public/shared/ui-bookmarks.js +231 -0
  108. package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
  109. package/neural-interface/public/shared/ui-command-runner.js +534 -0
  110. package/neural-interface/public/shared/ui-cost-widget.js +602 -0
  111. package/neural-interface/public/shared/ui-detail.js +1454 -0
  112. package/neural-interface/public/shared/ui-explorer.js +1356 -0
  113. package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
  114. package/neural-interface/public/shared/ui-games.js +411 -0
  115. package/neural-interface/public/shared/ui-help.js +107 -0
  116. package/neural-interface/public/shared/ui-image-gallery.js +461 -0
  117. package/neural-interface/public/shared/ui-invite.js +519 -0
  118. package/neural-interface/public/shared/ui-keybinds.js +452 -0
  119. package/neural-interface/public/shared/ui-layouts.js +297 -0
  120. package/neural-interface/public/shared/ui-link.js +731 -0
  121. package/neural-interface/public/shared/ui-loading.js +255 -0
  122. package/neural-interface/public/shared/ui-menubar.js +499 -0
  123. package/neural-interface/public/shared/ui-multiselect.js +93 -0
  124. package/neural-interface/public/shared/ui-navbar.js +222 -0
  125. package/neural-interface/public/shared/ui-notifications.js +287 -0
  126. package/neural-interface/public/shared/ui-panels.js +309 -0
  127. package/neural-interface/public/shared/ui-resume.js +653 -0
  128. package/neural-interface/public/shared/ui-search.js +145 -0
  129. package/neural-interface/public/shared/ui-sessions.js +531 -0
  130. package/neural-interface/public/shared/ui-settings.js +5987 -0
  131. package/neural-interface/public/shared/ui-sidebar.js +1905 -0
  132. package/neural-interface/public/shared/ui-skills.js +2805 -0
  133. package/neural-interface/public/shared/ui-stats.js +38 -0
  134. package/neural-interface/public/shared/ui-sync.js +201 -0
  135. package/neural-interface/public/shared/ui-terminal.js +6385 -0
  136. package/neural-interface/public/shared/ui-tooltip.js +123 -0
  137. package/neural-interface/public/shared/ui-trash.js +439 -0
  138. package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
  139. package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
  140. package/neural-interface/public/shared/ui-tutorial.js +1117 -0
  141. package/neural-interface/public/shared/ui-update.js +244 -0
  142. package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
  143. package/neural-interface/public/shared/ui-workspaces.js +722 -0
  144. package/neural-interface/public/shared/utils.js +360 -0
  145. package/neural-interface/public/shared/vterm-buffer.js +1102 -0
  146. package/neural-interface/public/sw.js +52 -0
  147. package/neural-interface/public/synabun.png +0 -0
  148. package/neural-interface/public/toolbar-album.svg +8 -0
  149. package/neural-interface/public/toolbar-question.svg +6 -0
  150. package/neural-interface/public/toolbar-sessions.svg +6 -0
  151. package/neural-interface/public/variant/2d/background.js +113 -0
  152. package/neural-interface/public/variant/2d/context-menu.js +133 -0
  153. package/neural-interface/public/variant/2d/gfx.js +45 -0
  154. package/neural-interface/public/variant/2d/graph.js +1635 -0
  155. package/neural-interface/public/variant/2d/hulls.js +207 -0
  156. package/neural-interface/public/variant/2d/lasso.js +135 -0
  157. package/neural-interface/public/variant/2d/layout.js +594 -0
  158. package/neural-interface/public/variant/2d/main.js +521 -0
  159. package/neural-interface/public/variant/2d/minimap.js +312 -0
  160. package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
  161. package/neural-interface/public/variant/3d/background.js +884 -0
  162. package/neural-interface/public/variant/3d/camera.js +661 -0
  163. package/neural-interface/public/variant/3d/gfx.js +116 -0
  164. package/neural-interface/public/variant/3d/graph.js +2399 -0
  165. package/neural-interface/public/variant/3d/main.js +500 -0
  166. package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
  167. package/neural-interface/scripts/rebuild-pty.js +100 -0
  168. package/neural-interface/server.js +15000 -0
  169. package/neural-interface/templates/CLAUDE-template.md +211 -0
  170. package/package.json +53 -0
  171. package/postinstall.js +135 -0
  172. package/setup.js +260 -0
  173. package/skills/leonardo/SKILL.md +159 -0
  174. package/skills/leonardo/icon.svg +1 -0
  175. package/skills/leonardo/modules/browser-guide.md +378 -0
  176. package/skills/leonardo/modules/image-prompter.md +277 -0
  177. package/skills/leonardo/modules/model-advisor.md +100 -0
  178. package/skills/leonardo/modules/prompt-library.md +110 -0
  179. package/skills/leonardo/modules/style-guide.md +186 -0
  180. package/skills/leonardo/modules/video-prompter.md +311 -0
  181. package/skills/synabun/SKILL.md +185 -0
  182. package/skills/synabun/icon.png +0 -0
  183. package/skills/synabun/modules/audit.md +374 -0
  184. package/skills/synabun/modules/changelog.md +194 -0
  185. package/skills/synabun/modules/idea.md +144 -0
  186. package/skills/synabun/modules/memorize.md +188 -0
  187. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,1102 @@
1
+ /**
2
+ * vterm-buffer.js — Virtual terminal buffer (2D cell grid)
3
+ *
4
+ * Implements the handler interface for AnsiParser. Maintains terminal
5
+ * state: cell grid, cursor, SGR attributes, scroll region, alternate
6
+ * screen buffer, and scrollback.
7
+ *
8
+ * Each cell: { char, fg, bg, bold, dim, italic, underline, inverse, strikethrough, url }
9
+ * Dirty row tracking via Set<rowIndex> for incremental rendering.
10
+ *
11
+ * Performance optimizations:
12
+ * - Circular buffer for scrollback (O(1) push/shift vs O(n) Array.shift)
13
+ * - Cell reuse in erase operations (mutate instead of replace)
14
+ * - getDirtyRows returns snapshot Array (safe across clearDirty calls)
15
+ */
16
+
17
+ import { AnsiParser } from './ansi-parser.js';
18
+
19
+ // ─── Default cell ──────────────────────────────────────────
20
+
21
+ function blankCell() {
22
+ return { char: ' ', fg: null, bg: null, bold: false, dim: false, italic: false, underline: false, inverse: false, strikethrough: false, url: null };
23
+ }
24
+
25
+ /** Reset an existing cell to blank state — avoids allocation */
26
+ function resetCell(c) {
27
+ c.char = ' '; c.fg = null; c.bg = null;
28
+ c.bold = false; c.dim = false; c.italic = false;
29
+ c.underline = false; c.inverse = false; c.strikethrough = false;
30
+ c.url = null;
31
+ }
32
+
33
+ function blankRow(cols) {
34
+ const row = new Array(cols);
35
+ for (let i = 0; i < cols; i++) row[i] = blankCell();
36
+ return row;
37
+ }
38
+
39
+ /** Reset all cells in a row to blank — reuses existing cell objects */
40
+ function resetRow(row, cols) {
41
+ for (let i = 0; i < cols; i++) {
42
+ if (row[i]) resetCell(row[i]);
43
+ else row[i] = blankCell();
44
+ }
45
+ }
46
+
47
+ // ─── Circular buffer for scrollback ────────────────────────
48
+ // Array.shift() is O(n) — moves every element. This is O(1) amortized.
49
+ class CircularBuffer {
50
+ constructor(capacity) {
51
+ this._capacity = capacity;
52
+ this._buf = new Array(capacity);
53
+ this._head = 0; // index of oldest element
54
+ this._size = 0;
55
+ }
56
+
57
+ get length() { return this._size; }
58
+
59
+ push(item) {
60
+ const idx = (this._head + this._size) % this._capacity;
61
+ this._buf[idx] = item;
62
+ if (this._size < this._capacity) {
63
+ this._size++;
64
+ } else {
65
+ // Overwrite oldest — advance head
66
+ this._head = (this._head + 1) % this._capacity;
67
+ }
68
+ }
69
+
70
+ get(index) {
71
+ if (index < 0 || index >= this._size) return null;
72
+ return this._buf[(this._head + index) % this._capacity];
73
+ }
74
+
75
+ clear() {
76
+ // Release references for GC
77
+ for (let i = 0; i < this._capacity; i++) this._buf[i] = undefined;
78
+ this._head = 0;
79
+ this._size = 0;
80
+ }
81
+
82
+ /** Transfer excess rows from array-shrink into scrollback */
83
+ pushMany(rows) {
84
+ for (let i = 0; i < rows.length; i++) this.push(rows[i]);
85
+ }
86
+ }
87
+
88
+ // ─── Standard 256-color palette (indices 0–255) ───────────
89
+ // 0-7: standard, 8-15: bright, 16-231: 6x6x6 cube, 232-255: grayscale
90
+ const PALETTE_256 = (() => {
91
+ const p = new Array(256);
92
+ // 0-15 handled by theme lookup at render time (null → use CSS class)
93
+ for (let i = 0; i < 16; i++) p[i] = null;
94
+ // 16-231: 6x6x6 color cube
95
+ for (let i = 16; i < 232; i++) {
96
+ const idx = i - 16;
97
+ const r = Math.floor(idx / 36);
98
+ const g = Math.floor((idx % 36) / 6);
99
+ const b = idx % 6;
100
+ p[i] = `rgb(${r ? r * 40 + 55 : 0},${g ? g * 40 + 55 : 0},${b ? b * 40 + 55 : 0})`;
101
+ }
102
+ // 232-255: grayscale
103
+ for (let i = 232; i < 256; i++) {
104
+ const v = (i - 232) * 10 + 8;
105
+ p[i] = `rgb(${v},${v},${v})`;
106
+ }
107
+ return p;
108
+ })();
109
+
110
+ const MAX_SCROLLBACK = 5000;
111
+
112
+ export class VTermBuffer {
113
+ /**
114
+ * @param {number} cols
115
+ * @param {number} rows
116
+ */
117
+ constructor(cols, rows) {
118
+ this.cols = cols;
119
+ this.rows = rows;
120
+
121
+ // Main screen buffer
122
+ this._mainBuffer = [];
123
+ for (let r = 0; r < rows; r++) this._mainBuffer.push(blankRow(cols));
124
+
125
+ // Alternate screen buffer (created on demand)
126
+ this._altBuffer = null;
127
+ this._useAlt = false;
128
+
129
+ // Active buffer reference
130
+ this._buffer = this._mainBuffer;
131
+
132
+ // Scrollback (main buffer only) — circular buffer for O(1) operations
133
+ this._scrollback = new CircularBuffer(MAX_SCROLLBACK);
134
+
135
+ // Cursor state
136
+ this._cursorX = 0;
137
+ this._cursorY = 0;
138
+ this._cursorVisible = true;
139
+
140
+ // Saved cursor (DECSC/DECRC)
141
+ this._savedCursor = null;
142
+ this._savedCursorAlt = null;
143
+
144
+ // SGR attributes
145
+ this._attr = { fg: null, bg: null, bold: false, dim: false, italic: false, underline: false, inverse: false, strikethrough: false };
146
+
147
+ // Scroll region (DECSTBM) — 0-based, inclusive
148
+ this._scrollTop = 0;
149
+ this._scrollBottom = rows - 1;
150
+
151
+ // Mode flags
152
+ this._bracketedPaste = false;
153
+ this._appCursorKeys = false;
154
+ this._autoWrap = true;
155
+ this._originMode = false;
156
+ this._insertMode = false;
157
+
158
+ // OSC state
159
+ this._title = '';
160
+ this._currentUrl = null; // for OSC 8 hyperlinks
161
+
162
+ // Dirty tracking
163
+ this._dirty = new Set();
164
+ this._allDirty = true; // first render paints everything
165
+
166
+ // Alt screen transition flag — renderer uses this to force scroll reset
167
+ this._altTransitionPending = false;
168
+
169
+ // Wrap-pending flag: cursor at right margin, next char wraps
170
+ this._wrapPending = false;
171
+
172
+ // Parser
173
+ this._parser = new AnsiParser(this);
174
+
175
+ // Charset (G0/G1) — simplified, just track if special
176
+ this._charsetG0 = null;
177
+ this._charsetG1 = null;
178
+ this._activeCharset = 0; // 0=G0, 1=G1
179
+ }
180
+
181
+ // ─── Public API ────────────────────────────────────────
182
+
183
+ /** Feed raw PTY data */
184
+ write(data) {
185
+ this._parser.parse(data);
186
+ }
187
+
188
+ /** Get a row from the visible buffer (0 = top of screen) */
189
+ getRow(idx) {
190
+ if (idx < 0 || idx >= this.rows) return null;
191
+ return this._buffer[idx];
192
+ }
193
+
194
+ /** Get scrollback row (0 = oldest) */
195
+ getScrollbackRow(idx) {
196
+ if (this._useAlt) return null;
197
+ return this._scrollback.get(idx);
198
+ }
199
+
200
+ get scrollbackLength() {
201
+ return this._useAlt ? 0 : this._scrollback.length;
202
+ }
203
+
204
+ /** Get dirty row indices since last clearDirty().
205
+ * Returns the internal Set when not allDirty — avoids Array.from allocation.
206
+ * Callers must iterate with for..of (Set is iterable). */
207
+ getDirtyRows() {
208
+ if (this._allDirty) {
209
+ const all = [];
210
+ for (let i = 0; i < this.rows; i++) all.push(i);
211
+ return all;
212
+ }
213
+ return Array.from(this._dirty);
214
+ }
215
+
216
+ clearDirty() {
217
+ this._dirty.clear();
218
+ this._allDirty = false;
219
+ }
220
+
221
+ get cursorX() { return this._cursorX; }
222
+ get cursorY() { return this._cursorY; }
223
+ get cursorVisible() { return this._cursorVisible; }
224
+ get title() { return this._title; }
225
+ get bracketedPaste() { return this._bracketedPaste; }
226
+ get appCursorKeys() { return this._appCursorKeys; }
227
+
228
+ /** Resize the terminal */
229
+ resize(cols, rows) {
230
+ if (cols === this.cols && rows === this.rows) return;
231
+ const oldCols = this.cols;
232
+ const oldRows = this.rows;
233
+ this.cols = cols;
234
+ this.rows = rows;
235
+
236
+ // Resize both buffers
237
+ this._resizeBuffer(this._mainBuffer, oldCols, oldRows, cols, rows, true);
238
+ if (this._altBuffer) {
239
+ this._resizeBuffer(this._altBuffer, oldCols, oldRows, cols, rows, false);
240
+ }
241
+ this._buffer = this._useAlt ? this._altBuffer : this._mainBuffer;
242
+
243
+ // Clamp cursor
244
+ this._cursorX = Math.min(this._cursorX, cols - 1);
245
+ this._cursorY = Math.min(this._cursorY, rows - 1);
246
+
247
+ // Reset scroll region
248
+ this._scrollTop = 0;
249
+ this._scrollBottom = rows - 1;
250
+
251
+ this._allDirty = true;
252
+ }
253
+
254
+ /** Get plain text content of a region (for copy).
255
+ * Row indices are absolute: 0..scrollbackLength-1 = scrollback,
256
+ * scrollbackLength..scrollbackLength+rows-1 = screen buffer.
257
+ */
258
+ getText(startRow, startCol, endRow, endCol) {
259
+ const sb = this.scrollbackLength;
260
+ const lines = [];
261
+ for (let r = startRow; r <= endRow; r++) {
262
+ const row = r < sb
263
+ ? this.getScrollbackRow(r)
264
+ : this.getRow(r - sb);
265
+ if (!row) continue;
266
+ const s = r === startRow ? startCol : 0;
267
+ const e = r === endRow ? endCol : this.cols - 1;
268
+ let line = '';
269
+ for (let c = s; c <= e; c++) {
270
+ line += row[c]?.char || ' ';
271
+ }
272
+ lines.push(line.replace(/\s+$/, ''));
273
+ }
274
+ return lines.join('\n');
275
+ }
276
+
277
+ // ─── Handler interface (called by AnsiParser) ──────────
278
+
279
+ /** Printable characters */
280
+ print(text) {
281
+ const ad = this._allDirty;
282
+ const cols = this.cols;
283
+ const attr = this._attr;
284
+ const url = this._currentUrl;
285
+
286
+ for (let i = 0; i < text.length; i++) {
287
+ const ch = text[i];
288
+
289
+ // Handle wrap-pending
290
+ if (this._wrapPending) {
291
+ if (this._autoWrap) {
292
+ if (!ad) this._dirty.add(this._cursorY);
293
+ this._cursorX = 0;
294
+ this._lineFeed();
295
+ }
296
+ this._wrapPending = false;
297
+ }
298
+
299
+ // Insert mode: shift chars right
300
+ if (this._insertMode && this._cursorX < cols) {
301
+ const row = this._buffer[this._cursorY];
302
+ for (let c = cols - 1; c > this._cursorX; c--) {
303
+ row[c] = row[c - 1];
304
+ }
305
+ }
306
+
307
+ // Write cell
308
+ const row = this._buffer[this._cursorY];
309
+ if (this._cursorX < cols) {
310
+ const cell = row[this._cursorX];
311
+ cell.char = ch;
312
+ cell.fg = attr.fg;
313
+ cell.bg = attr.bg;
314
+ cell.bold = attr.bold;
315
+ cell.dim = attr.dim;
316
+ cell.italic = attr.italic;
317
+ cell.underline = attr.underline;
318
+ cell.inverse = attr.inverse;
319
+ cell.strikethrough = attr.strikethrough;
320
+ cell.url = url;
321
+ if (!ad) this._dirty.add(this._cursorY);
322
+ }
323
+
324
+ // Advance cursor
325
+ if (this._cursorX >= cols - 1) {
326
+ this._wrapPending = true;
327
+ } else {
328
+ this._cursorX++;
329
+ }
330
+ }
331
+ }
332
+
333
+ /** C0 control codes */
334
+ execute(code) {
335
+ switch (code) {
336
+ case 0x08: // BS
337
+ if (this._cursorX > 0) this._cursorX--;
338
+ this._wrapPending = false;
339
+ break;
340
+ case 0x09: // HT (tab)
341
+ this._cursorX = Math.min(((this._cursorX >> 3) + 1) << 3, this.cols - 1);
342
+ this._wrapPending = false;
343
+ break;
344
+ case 0x0a: // LF
345
+ case 0x0b: // VT
346
+ case 0x0c: // FF
347
+ this._lineFeed();
348
+ break;
349
+ case 0x0d: // CR
350
+ this._cursorX = 0;
351
+ this._wrapPending = false;
352
+ break;
353
+ case 0x07: // BEL
354
+ // Could trigger visual bell — currently ignored
355
+ break;
356
+ }
357
+ }
358
+
359
+ /** CSI dispatch */
360
+ csiDispatch(params, privateMarker, intermediates, final) {
361
+ if (privateMarker === '?') {
362
+ this._csiPrivate(params, final);
363
+ return;
364
+ }
365
+ if (privateMarker === '>') {
366
+ // DA2 and similar — ignore
367
+ return;
368
+ }
369
+
370
+ const p0 = this._getParam(params, 0, 1);
371
+ const p1 = this._getParam(params, 1, 1);
372
+
373
+ switch (final) {
374
+ // ─── Cursor movement ─────────────────────────────
375
+ case 'A': // CUU — cursor up
376
+ this._cursorY = Math.max(this._scrollTop, this._cursorY - p0);
377
+ this._wrapPending = false;
378
+ break;
379
+ case 'B': // CUD — cursor down
380
+ this._cursorY = Math.min(this._scrollBottom, this._cursorY + p0);
381
+ this._wrapPending = false;
382
+ break;
383
+ case 'C': // CUF — cursor forward
384
+ this._cursorX = Math.min(this.cols - 1, this._cursorX + p0);
385
+ this._wrapPending = false;
386
+ break;
387
+ case 'D': // CUB — cursor back
388
+ this._cursorX = Math.max(0, this._cursorX - p0);
389
+ this._wrapPending = false;
390
+ break;
391
+ case 'E': // CNL — cursor next line
392
+ this._cursorX = 0;
393
+ this._cursorY = Math.min(this._scrollBottom, this._cursorY + p0);
394
+ this._wrapPending = false;
395
+ break;
396
+ case 'F': // CPL — cursor previous line
397
+ this._cursorX = 0;
398
+ this._cursorY = Math.max(this._scrollTop, this._cursorY - p0);
399
+ this._wrapPending = false;
400
+ break;
401
+ case 'G': // CHA — cursor horizontal absolute
402
+ this._cursorX = Math.min(Math.max(p0 - 1, 0), this.cols - 1);
403
+ this._wrapPending = false;
404
+ break;
405
+ case 'H': // CUP — cursor position
406
+ case 'f': // HVP — same as CUP
407
+ this._cursorY = Math.min(Math.max(p0 - 1, 0), this.rows - 1);
408
+ this._cursorX = Math.min(Math.max(p1 - 1, 0), this.cols - 1);
409
+ this._wrapPending = false;
410
+ break;
411
+ case 'd': // VPA — vertical line position absolute
412
+ this._cursorY = Math.min(Math.max(p0 - 1, 0), this.rows - 1);
413
+ this._wrapPending = false;
414
+ break;
415
+
416
+ // ─── Erase ───────────────────────────────────────
417
+ case 'J': // ED — erase in display
418
+ this._eraseDisplay(this._getParam(params, 0, 0));
419
+ break;
420
+ case 'K': // EL — erase in line
421
+ this._eraseLine(this._getParam(params, 0, 0));
422
+ break;
423
+
424
+ // ─── Insert/Delete ───────────────────────────────
425
+ case 'L': // IL — insert lines
426
+ this._insertLines(p0);
427
+ break;
428
+ case 'M': // DL — delete lines
429
+ this._deleteLines(p0);
430
+ break;
431
+ case '@': // ICH — insert characters
432
+ this._insertChars(p0);
433
+ break;
434
+ case 'P': // DCH — delete characters
435
+ this._deleteChars(p0);
436
+ break;
437
+ case 'X': // ECH — erase characters
438
+ this._eraseChars(p0);
439
+ break;
440
+
441
+ // ─── Scroll ──────────────────────────────────────
442
+ case 'S': // SU — scroll up
443
+ this._scrollUp(p0);
444
+ break;
445
+ case 'T': // SD — scroll down
446
+ this._scrollDown(p0);
447
+ break;
448
+
449
+ // ─── SGR ─────────────────────────────────────────
450
+ case 'm': // SGR — select graphic rendition
451
+ this._handleSGR(params);
452
+ break;
453
+
454
+ // ─── Scroll region ──────────────────────────────
455
+ case 'r': // DECSTBM — set top and bottom margins
456
+ this._scrollTop = Math.max(0, (this._getParam(params, 0, 1)) - 1);
457
+ this._scrollBottom = Math.min(this.rows - 1, (this._getParam(params, 1, this.rows)) - 1);
458
+ // Move cursor to home position
459
+ this._cursorX = 0;
460
+ this._cursorY = this._originMode ? this._scrollTop : 0;
461
+ this._wrapPending = false;
462
+ break;
463
+
464
+ // ─── Tab clear ──────────────────────────────────
465
+ case 'g': // TBC — tab clear (ignore, we use fixed 8-col tabs)
466
+ break;
467
+
468
+ // ─── Device Status Report ───────────────────────
469
+ case 'n': // DSR
470
+ // p0 === 6 → report cursor position (not needed for rendering, but some apps expect it)
471
+ break;
472
+
473
+ // ─── Cursor save/restore (ANSI) ─────────────────
474
+ case 's': // SCP — save cursor position
475
+ this._savedCursor = { x: this._cursorX, y: this._cursorY, attr: { ...this._attr } };
476
+ break;
477
+ case 'u': // RCP — restore cursor position
478
+ if (this._savedCursor) {
479
+ this._cursorX = this._savedCursor.x;
480
+ this._cursorY = this._savedCursor.y;
481
+ this._attr = { ...this._savedCursor.attr };
482
+ }
483
+ this._wrapPending = false;
484
+ break;
485
+
486
+ // ─── Repeat ─────────────────────────────────────
487
+ case 'b': // REP — repeat preceding character
488
+ // Not commonly used by CLI tools, safe to ignore
489
+ break;
490
+
491
+ // ─── Set mode (SM) / Reset mode (RM) ────────────
492
+ case 'h': // SM
493
+ if (this._getParam(params, 0, 0) === 4) this._insertMode = true;
494
+ break;
495
+ case 'l': // RM
496
+ if (this._getParam(params, 0, 0) === 4) this._insertMode = false;
497
+ break;
498
+
499
+ default:
500
+ // Unhandled CSI — silently ignore
501
+ break;
502
+ }
503
+ }
504
+
505
+ /** ESC dispatch (simple escape sequences) */
506
+ escDispatch(intermediates, final) {
507
+ if (intermediates === '') {
508
+ switch (final) {
509
+ case '7': // DECSC — save cursor
510
+ this._saveCursor();
511
+ break;
512
+ case '8': // DECRC — restore cursor
513
+ this._restoreCursor();
514
+ break;
515
+ case 'D': // IND — index (line feed without CR)
516
+ this._lineFeed();
517
+ break;
518
+ case 'E': // NEL — next line (LF + CR)
519
+ this._cursorX = 0;
520
+ this._lineFeed();
521
+ break;
522
+ case 'M': // RI — reverse index
523
+ this._reverseIndex();
524
+ break;
525
+ case 'c': // RIS — full reset
526
+ this._fullReset();
527
+ break;
528
+ case 'H': // HTS — horizontal tab set (ignore, fixed tabs)
529
+ break;
530
+ }
531
+ } else if (intermediates === '(') {
532
+ // G0 charset designation
533
+ this._charsetG0 = final === '0' ? 'special' : null;
534
+ } else if (intermediates === ')') {
535
+ // G1 charset designation
536
+ this._charsetG1 = final === '0' ? 'special' : null;
537
+ } else if (intermediates === '#') {
538
+ // DECDHL, DECSWL, DECDWL — double height/width (ignore)
539
+ }
540
+ }
541
+
542
+ /** OSC dispatch */
543
+ oscDispatch(data) {
544
+ const semiIdx = data.indexOf(';');
545
+ if (semiIdx === -1) return;
546
+ const cmd = data.substring(0, semiIdx);
547
+ const payload = data.substring(semiIdx + 1);
548
+
549
+ switch (cmd) {
550
+ case '0': // Set title + icon name
551
+ case '2': // Set title
552
+ this._title = payload;
553
+ break;
554
+ case '8': { // Hyperlink
555
+ // OSC 8 ; params ; uri ST
556
+ // params are key=value pairs separated by :
557
+ // empty uri = close link
558
+ const semiIdx2 = payload.indexOf(';');
559
+ if (semiIdx2 !== -1) {
560
+ const uri = payload.substring(semiIdx2 + 1);
561
+ this._currentUrl = uri || null;
562
+ }
563
+ break;
564
+ }
565
+ // OSC 4: set color palette — ignored
566
+ // OSC 10/11: fg/bg color — ignored
567
+ }
568
+ }
569
+
570
+ // ─── Private mode (CSI ? ...) ──────────────────────────
571
+
572
+ _csiPrivate(params, final) {
573
+ const mode = this._getParam(params, 0, 0);
574
+
575
+ switch (final) {
576
+ case 'h': // DECSET
577
+ this._setPrivateMode(mode, true);
578
+ // Handle multiple params
579
+ for (let i = 1; i < params.length; i++) {
580
+ const m = typeof params[i] === 'number' ? params[i] : 0;
581
+ if (m) this._setPrivateMode(m, true);
582
+ }
583
+ break;
584
+ case 'l': // DECRST
585
+ this._setPrivateMode(mode, false);
586
+ for (let i = 1; i < params.length; i++) {
587
+ const m = typeof params[i] === 'number' ? params[i] : 0;
588
+ if (m) this._setPrivateMode(m, false);
589
+ }
590
+ break;
591
+ }
592
+ }
593
+
594
+ _setPrivateMode(mode, enable) {
595
+ switch (mode) {
596
+ case 1: // DECCKM — application cursor keys
597
+ this._appCursorKeys = enable;
598
+ break;
599
+ case 7: // DECAWM — auto-wrap
600
+ this._autoWrap = enable;
601
+ break;
602
+ case 25: // DECTCEM — cursor visible
603
+ this._cursorVisible = enable;
604
+ break;
605
+ case 1049: // Alt screen + save/restore cursor
606
+ if (enable) {
607
+ this._saveCursor();
608
+ this._switchToAltBuffer();
609
+ } else {
610
+ this._switchToMainBuffer();
611
+ this._restoreCursor();
612
+ }
613
+ break;
614
+ case 47: // Alt screen (no save/restore)
615
+ case 1047:
616
+ if (enable) {
617
+ this._switchToAltBuffer();
618
+ } else {
619
+ this._switchToMainBuffer();
620
+ }
621
+ break;
622
+ case 2004: // Bracketed paste
623
+ this._bracketedPaste = enable;
624
+ break;
625
+ case 6: // DECOM — origin mode
626
+ this._originMode = enable;
627
+ if (enable) {
628
+ this._cursorX = 0;
629
+ this._cursorY = this._scrollTop;
630
+ }
631
+ break;
632
+ case 12: // AT&T cursor blink — ignore
633
+ break;
634
+ case 1000: // Mouse tracking — ignore for now
635
+ case 1002:
636
+ case 1003:
637
+ case 1006:
638
+ break;
639
+ }
640
+ }
641
+
642
+ // ─── SGR (Select Graphic Rendition) ────────────────────
643
+
644
+ _handleSGR(params) {
645
+ if (params.length === 0) {
646
+ this._resetAttr();
647
+ return;
648
+ }
649
+
650
+ for (let i = 0; i < params.length; i++) {
651
+ const p = params[i];
652
+
653
+ // Handle colon-separated sub-params (e.g., 38:2:r:g:b)
654
+ if (Array.isArray(p)) {
655
+ this._handleSGRSubParams(p);
656
+ continue;
657
+ }
658
+
659
+ switch (p) {
660
+ case 0:
661
+ this._resetAttr();
662
+ break;
663
+ case 1: this._attr.bold = true; break;
664
+ case 2: this._attr.dim = true; break;
665
+ case 3: this._attr.italic = true; break;
666
+ case 4: this._attr.underline = true; break;
667
+ case 7: this._attr.inverse = true; break;
668
+ case 9: this._attr.strikethrough = true; break;
669
+ case 21: this._attr.underline = true; break; // double underline → underline
670
+ case 22: this._attr.bold = false; this._attr.dim = false; break;
671
+ case 23: this._attr.italic = false; break;
672
+ case 24: this._attr.underline = false; break;
673
+ case 27: this._attr.inverse = false; break;
674
+ case 29: this._attr.strikethrough = false; break;
675
+
676
+ // Foreground colors (standard)
677
+ case 30: case 31: case 32: case 33:
678
+ case 34: case 35: case 36: case 37:
679
+ this._attr.fg = p - 30;
680
+ break;
681
+ case 39: // Default fg
682
+ this._attr.fg = null;
683
+ break;
684
+
685
+ // Background colors (standard)
686
+ case 40: case 41: case 42: case 43:
687
+ case 44: case 45: case 46: case 47:
688
+ this._attr.bg = p - 40;
689
+ break;
690
+ case 49: // Default bg
691
+ this._attr.bg = null;
692
+ break;
693
+
694
+ // Bright foreground
695
+ case 90: case 91: case 92: case 93:
696
+ case 94: case 95: case 96: case 97:
697
+ this._attr.fg = p - 90 + 8;
698
+ break;
699
+
700
+ // Bright background
701
+ case 100: case 101: case 102: case 103:
702
+ case 104: case 105: case 106: case 107:
703
+ this._attr.bg = p - 100 + 8;
704
+ break;
705
+
706
+ // Extended colors (semicolon-separated)
707
+ case 38: { // FG extended
708
+ const result = this._parseExtendedColor(params, i + 1);
709
+ if (result) {
710
+ this._attr.fg = result.color;
711
+ i = result.nextIndex - 1; // -1 because loop increments
712
+ }
713
+ break;
714
+ }
715
+ case 48: { // BG extended
716
+ const result = this._parseExtendedColor(params, i + 1);
717
+ if (result) {
718
+ this._attr.bg = result.color;
719
+ i = result.nextIndex - 1;
720
+ }
721
+ break;
722
+ }
723
+ case 58: { // Underline color (ignore value but consume params)
724
+ const result = this._parseExtendedColor(params, i + 1);
725
+ if (result) i = result.nextIndex - 1;
726
+ break;
727
+ }
728
+ }
729
+ }
730
+ }
731
+
732
+ /** Handle colon-separated SGR sub-params like 38:2:r:g:b */
733
+ _handleSGRSubParams(subParams) {
734
+ if (subParams.length < 2) return;
735
+ const type = subParams[0];
736
+ const colorType = subParams[1];
737
+
738
+ if (type === 38 || type === 48) {
739
+ // FG or BG
740
+ const isFg = type === 38;
741
+ if (colorType === 2 && subParams.length >= 5) {
742
+ // Truecolor: 38:2:r:g:b or 38:2:colorspace:r:g:b
743
+ let r, g, b;
744
+ if (subParams.length >= 6) {
745
+ // With colorspace: 38:2:cs:r:g:b
746
+ r = subParams[3]; g = subParams[4]; b = subParams[5];
747
+ } else {
748
+ r = subParams[2]; g = subParams[3]; b = subParams[4];
749
+ }
750
+ const color = `rgb(${r & 255},${g & 255},${b & 255})`;
751
+ if (isFg) this._attr.fg = color; else this._attr.bg = color;
752
+ } else if (colorType === 5 && subParams.length >= 3) {
753
+ // 256-color: 38:5:idx
754
+ this._set256Color(isFg, subParams[2]);
755
+ }
756
+ }
757
+ }
758
+
759
+ /**
760
+ * Parse extended color params (semicolon-separated):
761
+ * 38;2;r;g;b or 38;5;idx
762
+ * @returns {{ color: any, nextIndex: number }} or null
763
+ */
764
+ _parseExtendedColor(params, startIdx) {
765
+ if (startIdx >= params.length) return null;
766
+ const mode = typeof params[startIdx] === 'number' ? params[startIdx] : 0;
767
+
768
+ if (mode === 2) {
769
+ // Truecolor
770
+ if (startIdx + 3 < params.length) {
771
+ const r = typeof params[startIdx + 1] === 'number' ? params[startIdx + 1] : 0;
772
+ const g = typeof params[startIdx + 2] === 'number' ? params[startIdx + 2] : 0;
773
+ const b = typeof params[startIdx + 3] === 'number' ? params[startIdx + 3] : 0;
774
+ return { color: `rgb(${r & 255},${g & 255},${b & 255})`, nextIndex: startIdx + 4 };
775
+ }
776
+ } else if (mode === 5) {
777
+ // 256-color
778
+ if (startIdx + 1 < params.length) {
779
+ const idx = typeof params[startIdx + 1] === 'number' ? params[startIdx + 1] : 0;
780
+ const color = this._resolve256(idx);
781
+ return { color, nextIndex: startIdx + 2 };
782
+ }
783
+ }
784
+ return null;
785
+ }
786
+
787
+ _resolve256(idx) {
788
+ if (idx < 0 || idx > 255) return null;
789
+ if (idx < 16) return idx; // Theme color index (rendered via CSS class)
790
+ return PALETTE_256[idx]; // RGB string
791
+ }
792
+
793
+ _set256Color(isFg, idx) {
794
+ const color = this._resolve256(idx);
795
+ if (isFg) this._attr.fg = color; else this._attr.bg = color;
796
+ }
797
+
798
+ _resetAttr() {
799
+ this._attr.fg = null;
800
+ this._attr.bg = null;
801
+ this._attr.bold = false;
802
+ this._attr.dim = false;
803
+ this._attr.italic = false;
804
+ this._attr.underline = false;
805
+ this._attr.inverse = false;
806
+ this._attr.strikethrough = false;
807
+ }
808
+
809
+ // ─── Line operations ──────────────────────────────────
810
+
811
+ _lineFeed() {
812
+ this._wrapPending = false;
813
+ if (this._cursorY === this._scrollBottom) {
814
+ this._scrollUp(1);
815
+ } else if (this._cursorY < this.rows - 1) {
816
+ this._cursorY++;
817
+ }
818
+ }
819
+
820
+ _reverseIndex() {
821
+ this._wrapPending = false;
822
+ if (this._cursorY === this._scrollTop) {
823
+ this._scrollDown(1);
824
+ } else if (this._cursorY > 0) {
825
+ this._cursorY--;
826
+ }
827
+ }
828
+
829
+ _scrollUp(n) {
830
+ for (let i = 0; i < n; i++) {
831
+ // Push top line to scrollback (main buffer only, full-screen scroll region)
832
+ if (!this._useAlt && this._scrollTop === 0) {
833
+ this._scrollback.push(this._buffer[this._scrollTop]);
834
+ }
835
+ // Shift rows up within scroll region
836
+ for (let r = this._scrollTop; r < this._scrollBottom; r++) {
837
+ this._buffer[r] = this._buffer[r + 1];
838
+ }
839
+ this._buffer[this._scrollBottom] = blankRow(this.cols);
840
+ }
841
+ // Mark all rows in scroll region dirty
842
+ if (!this._allDirty) {
843
+ for (let r = this._scrollTop; r <= this._scrollBottom; r++) {
844
+ this._dirty.add(r);
845
+ }
846
+ }
847
+ }
848
+
849
+ _scrollDown(n) {
850
+ for (let i = 0; i < n; i++) {
851
+ for (let r = this._scrollBottom; r > this._scrollTop; r--) {
852
+ this._buffer[r] = this._buffer[r - 1];
853
+ }
854
+ this._buffer[this._scrollTop] = blankRow(this.cols);
855
+ }
856
+ if (!this._allDirty) {
857
+ for (let r = this._scrollTop; r <= this._scrollBottom; r++) {
858
+ this._dirty.add(r);
859
+ }
860
+ }
861
+ }
862
+
863
+ _insertLines(n) {
864
+ const top = this._cursorY;
865
+ const bot = this._scrollBottom;
866
+ for (let i = 0; i < n; i++) {
867
+ for (let r = bot; r > top; r--) {
868
+ this._buffer[r] = this._buffer[r - 1];
869
+ }
870
+ this._buffer[top] = blankRow(this.cols);
871
+ }
872
+ if (!this._allDirty) {
873
+ for (let r = top; r <= bot; r++) this._dirty.add(r);
874
+ }
875
+ }
876
+
877
+ _deleteLines(n) {
878
+ const top = this._cursorY;
879
+ const bot = this._scrollBottom;
880
+ for (let i = 0; i < n; i++) {
881
+ for (let r = top; r < bot; r++) {
882
+ this._buffer[r] = this._buffer[r + 1];
883
+ }
884
+ this._buffer[bot] = blankRow(this.cols);
885
+ }
886
+ if (!this._allDirty) {
887
+ for (let r = top; r <= bot; r++) this._dirty.add(r);
888
+ }
889
+ }
890
+
891
+ // ─── Character operations ─────────────────────────────
892
+
893
+ _insertChars(n) {
894
+ const row = this._buffer[this._cursorY];
895
+ for (let i = 0; i < n; i++) {
896
+ row.pop();
897
+ row.splice(this._cursorX, 0, blankCell());
898
+ }
899
+ // Ensure row stays correct length
900
+ while (row.length < this.cols) row.push(blankCell());
901
+ if (row.length > this.cols) row.length = this.cols;
902
+ if (!this._allDirty) this._dirty.add(this._cursorY);
903
+ }
904
+
905
+ _deleteChars(n) {
906
+ const row = this._buffer[this._cursorY];
907
+ row.splice(this._cursorX, n);
908
+ while (row.length < this.cols) row.push(blankCell());
909
+ if (!this._allDirty) this._dirty.add(this._cursorY);
910
+ }
911
+
912
+ _eraseChars(n) {
913
+ const row = this._buffer[this._cursorY];
914
+ const end = Math.min(this._cursorX + n, this.cols);
915
+ for (let c = this._cursorX; c < end; c++) {
916
+ resetCell(row[c]);
917
+ }
918
+ if (!this._allDirty) this._dirty.add(this._cursorY);
919
+ }
920
+
921
+ // ─── Erase operations ─────────────────────────────────
922
+
923
+ _eraseDisplay(mode) {
924
+ const ad = this._allDirty;
925
+ switch (mode) {
926
+ case 0: // Cursor to end
927
+ this._eraseLineFrom(this._cursorY, this._cursorX);
928
+ for (let r = this._cursorY + 1; r < this.rows; r++) {
929
+ resetRow(this._buffer[r], this.cols);
930
+ if (!ad) this._dirty.add(r);
931
+ }
932
+ break;
933
+ case 1: // Start to cursor
934
+ for (let r = 0; r < this._cursorY; r++) {
935
+ resetRow(this._buffer[r], this.cols);
936
+ if (!ad) this._dirty.add(r);
937
+ }
938
+ this._eraseLineTo(this._cursorY, this._cursorX);
939
+ break;
940
+ case 2: // Entire display
941
+ for (let r = 0; r < this.rows; r++) {
942
+ resetRow(this._buffer[r], this.cols);
943
+ }
944
+ this._allDirty = true;
945
+ break;
946
+ case 3: // Entire display + scrollback
947
+ for (let r = 0; r < this.rows; r++) {
948
+ resetRow(this._buffer[r], this.cols);
949
+ }
950
+ this._scrollback.clear();
951
+ this._allDirty = true;
952
+ break;
953
+ }
954
+ }
955
+
956
+ _eraseLine(mode) {
957
+ switch (mode) {
958
+ case 0: // Cursor to end
959
+ this._eraseLineFrom(this._cursorY, this._cursorX);
960
+ break;
961
+ case 1: // Start to cursor
962
+ this._eraseLineTo(this._cursorY, this._cursorX);
963
+ break;
964
+ case 2: // Entire line
965
+ resetRow(this._buffer[this._cursorY], this.cols);
966
+ if (!this._allDirty) this._dirty.add(this._cursorY);
967
+ break;
968
+ }
969
+ }
970
+
971
+ _eraseLineFrom(row, col) {
972
+ const r = this._buffer[row];
973
+ for (let c = col; c < this.cols; c++) {
974
+ resetCell(r[c]);
975
+ }
976
+ if (!this._allDirty) this._dirty.add(row);
977
+ }
978
+
979
+ _eraseLineTo(row, col) {
980
+ const r = this._buffer[row];
981
+ for (let c = 0; c <= col && c < this.cols; c++) {
982
+ resetCell(r[c]);
983
+ }
984
+ if (!this._allDirty) this._dirty.add(row);
985
+ }
986
+
987
+ // ─── Cursor save/restore ──────────────────────────────
988
+
989
+ _saveCursor() {
990
+ const saved = { x: this._cursorX, y: this._cursorY, attr: { ...this._attr } };
991
+ if (this._useAlt) {
992
+ this._savedCursorAlt = saved;
993
+ } else {
994
+ this._savedCursor = saved;
995
+ }
996
+ }
997
+
998
+ _restoreCursor() {
999
+ const saved = this._useAlt ? this._savedCursorAlt : this._savedCursor;
1000
+ if (saved) {
1001
+ this._cursorX = Math.min(saved.x, this.cols - 1);
1002
+ this._cursorY = Math.min(saved.y, this.rows - 1);
1003
+ this._attr = { ...saved.attr };
1004
+ }
1005
+ this._wrapPending = false;
1006
+ }
1007
+
1008
+ // ─── Alt screen buffer ────────────────────────────────
1009
+
1010
+ _switchToAltBuffer() {
1011
+ if (this._useAlt) return;
1012
+ // Create fresh alt buffer
1013
+ this._altBuffer = [];
1014
+ for (let r = 0; r < this.rows; r++) this._altBuffer.push(blankRow(this.cols));
1015
+ this._useAlt = true;
1016
+ this._buffer = this._altBuffer;
1017
+ this._allDirty = true;
1018
+ this._altTransitionPending = true;
1019
+ }
1020
+
1021
+ _switchToMainBuffer() {
1022
+ if (!this._useAlt) return;
1023
+ this._useAlt = false;
1024
+ this._altBuffer = null;
1025
+ this._buffer = this._mainBuffer;
1026
+ this._allDirty = true;
1027
+ this._altTransitionPending = true;
1028
+ }
1029
+
1030
+ // ─── Full reset ───────────────────────────────────────
1031
+
1032
+ _fullReset() {
1033
+ this._cursorX = 0;
1034
+ this._cursorY = 0;
1035
+ this._cursorVisible = true;
1036
+ this._resetAttr();
1037
+ this._scrollTop = 0;
1038
+ this._scrollBottom = this.rows - 1;
1039
+ this._bracketedPaste = false;
1040
+ this._appCursorKeys = false;
1041
+ this._autoWrap = true;
1042
+ this._originMode = false;
1043
+ this._insertMode = false;
1044
+ this._title = '';
1045
+ this._currentUrl = null;
1046
+ this._wrapPending = false;
1047
+ this._savedCursor = null;
1048
+ this._savedCursorAlt = null;
1049
+
1050
+ if (this._useAlt) {
1051
+ this._useAlt = false;
1052
+ this._altBuffer = null;
1053
+ this._buffer = this._mainBuffer;
1054
+ }
1055
+
1056
+ for (let r = 0; r < this.rows; r++) {
1057
+ resetRow(this._buffer[r], this.cols);
1058
+ }
1059
+ this._scrollback.clear();
1060
+ this._allDirty = true;
1061
+ this._parser.reset();
1062
+ }
1063
+
1064
+ // ─── Buffer resize ────────────────────────────────────
1065
+
1066
+ _resizeBuffer(buffer, oldCols, oldRows, newCols, newRows, isMain) {
1067
+ // Adjust row count
1068
+ while (buffer.length < newRows) {
1069
+ buffer.push(blankRow(newCols));
1070
+ }
1071
+ // If shrinking rows, push excess to scrollback (main only)
1072
+ while (buffer.length > newRows) {
1073
+ const removed = buffer.shift();
1074
+ if (isMain) {
1075
+ this._scrollback.push(removed);
1076
+ }
1077
+ }
1078
+ // Adjust column count per row
1079
+ if (newCols !== oldCols) {
1080
+ for (let r = 0; r < buffer.length; r++) {
1081
+ const row = buffer[r];
1082
+ if (newCols > oldCols) {
1083
+ // Expand
1084
+ for (let c = oldCols; c < newCols; c++) row.push(blankCell());
1085
+ } else {
1086
+ // Shrink
1087
+ row.length = newCols;
1088
+ }
1089
+ }
1090
+ }
1091
+ }
1092
+
1093
+ // ─── Helpers ──────────────────────────────────────────
1094
+
1095
+ _getParam(params, idx, def) {
1096
+ if (idx >= params.length) return def;
1097
+ const v = params[idx];
1098
+ if (typeof v === 'number') return v || def;
1099
+ if (Array.isArray(v)) return v[0] || def;
1100
+ return def;
1101
+ }
1102
+ }