erosolar-cli 1.7.194 → 1.7.196
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/dist/core/agent.d.ts +6 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +10 -1
- package/dist/core/agent.js.map +1 -1
- package/dist/core/errors/errorUtils.d.ts +87 -0
- package/dist/core/errors/errorUtils.d.ts.map +1 -0
- package/dist/core/errors/errorUtils.js +158 -0
- package/dist/core/errors/errorUtils.js.map +1 -0
- package/dist/core/resultVerification.js.map +1 -1
- package/dist/core/toolValidation.d.ts +117 -0
- package/dist/core/toolValidation.d.ts.map +1 -0
- package/dist/core/toolValidation.js +282 -0
- package/dist/core/toolValidation.js.map +1 -0
- package/dist/core/types/utilityTypes.d.ts +192 -0
- package/dist/core/types/utilityTypes.d.ts.map +1 -0
- package/dist/core/types/utilityTypes.js +272 -0
- package/dist/core/types/utilityTypes.js.map +1 -0
- package/dist/shell/interactiveShell.d.ts +9 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +69 -1
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/systemPrompt.d.ts.map +1 -1
- package/dist/shell/systemPrompt.js +5 -0
- package/dist/shell/systemPrompt.js.map +1 -1
- package/dist/shell/terminalInput.d.ts +1 -0
- package/dist/shell/terminalInput.d.ts.map +1 -1
- package/dist/shell/terminalInput.js +9 -2
- package/dist/shell/terminalInput.js.map +1 -1
- package/dist/shell/terminalInputAdapter.d.ts +4 -0
- package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
- package/dist/shell/terminalInputAdapter.js +6 -0
- package/dist/shell/terminalInputAdapter.js.map +1 -1
- package/dist/tools/planningTools.d.ts +1 -0
- package/dist/tools/planningTools.d.ts.map +1 -1
- package/dist/tools/planningTools.js +48 -0
- package/dist/tools/planningTools.js.map +1 -1
- package/dist/ui/display.d.ts +5 -49
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +36 -335
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/toolDisplay.d.ts.map +1 -1
- package/dist/ui/toolDisplay.js +17 -0
- package/dist/ui/toolDisplay.js.map +1 -1
- package/dist/utils/planFormatter.d.ts +34 -0
- package/dist/utils/planFormatter.d.ts.map +1 -0
- package/dist/utils/planFormatter.js +140 -0
- package/dist/utils/planFormatter.js.map +1 -0
- package/package.json +2 -2
- package/dist/shell/bracketedPasteManager.d.ts +0 -128
- package/dist/shell/bracketedPasteManager.d.ts.map +0 -1
- package/dist/shell/bracketedPasteManager.enhanced.d.ts +0 -2
- package/dist/shell/bracketedPasteManager.enhanced.d.ts.map +0 -1
- package/dist/shell/bracketedPasteManager.enhanced.js +0 -4
- package/dist/shell/bracketedPasteManager.enhanced.js.map +0 -1
- package/dist/shell/bracketedPasteManager.js +0 -372
- package/dist/shell/bracketedPasteManager.js.map +0 -1
- package/dist/shell/chatBox.d.ts +0 -228
- package/dist/shell/chatBox.d.ts.map +0 -1
- package/dist/shell/chatBox.js +0 -811
- package/dist/shell/chatBox.js.map +0 -1
- package/dist/shell/unifiedChatBox.d.ts +0 -194
- package/dist/shell/unifiedChatBox.d.ts.map +0 -1
- package/dist/shell/unifiedChatBox.js +0 -585
- package/dist/shell/unifiedChatBox.js.map +0 -1
- package/dist/ui/persistentPrompt.d.ts +0 -545
- package/dist/ui/persistentPrompt.d.ts.map +0 -1
- package/dist/ui/persistentPrompt.js +0 -1529
- package/dist/ui/persistentPrompt.js.map +0 -1
package/dist/shell/chatBox.js
DELETED
|
@@ -1,811 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ChatBox - Claude Code style input box at the bottom of terminal
|
|
3
|
-
*
|
|
4
|
-
* This is the ONLY place the cursor can ever be. Features:
|
|
5
|
-
* - Multi-line text input
|
|
6
|
-
* - Cursor navigation (arrows, home, end, word jump)
|
|
7
|
-
* - Line wrapping for long lines
|
|
8
|
-
* - Scrolling for content exceeding visible height
|
|
9
|
-
* - Always visible at bottom of terminal
|
|
10
|
-
* - Scroll region management for output area above
|
|
11
|
-
*/
|
|
12
|
-
import { EventEmitter } from 'node:events';
|
|
13
|
-
import * as readline from 'node:readline';
|
|
14
|
-
import { theme } from '../ui/theme.js';
|
|
15
|
-
/**
|
|
16
|
-
* ANSI escape codes for terminal control
|
|
17
|
-
*/
|
|
18
|
-
const ANSI = {
|
|
19
|
-
SAVE_CURSOR: '\u001b7',
|
|
20
|
-
RESTORE_CURSOR: '\u001b8',
|
|
21
|
-
CURSOR_TO: (row, col) => `\u001b[${row};${col}H`,
|
|
22
|
-
CURSOR_UP: (n) => `\u001b[${n}A`,
|
|
23
|
-
CURSOR_DOWN: (n) => `\u001b[${n}B`,
|
|
24
|
-
CURSOR_FORWARD: (n) => `\u001b[${n}C`,
|
|
25
|
-
CURSOR_BACK: (n) => `\u001b[${n}D`,
|
|
26
|
-
CLEAR_LINE: '\u001b[2K',
|
|
27
|
-
CLEAR_TO_END: '\u001b[0J',
|
|
28
|
-
CLEAR_TO_START: '\u001b[1J',
|
|
29
|
-
HIDE_CURSOR: '\u001b[?25l',
|
|
30
|
-
SHOW_CURSOR: '\u001b[?25h',
|
|
31
|
-
SET_SCROLL_REGION: (top, bottom) => `\u001b[${top};${bottom}r`,
|
|
32
|
-
RESET_SCROLL_REGION: '\u001b[r',
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* ChatBox - A multi-line text input widget fixed at the bottom of the terminal
|
|
36
|
-
*/
|
|
37
|
-
export class ChatBox extends EventEmitter {
|
|
38
|
-
config;
|
|
39
|
-
writeStream;
|
|
40
|
-
readStream;
|
|
41
|
-
state = {
|
|
42
|
-
lines: [''],
|
|
43
|
-
cursorRow: 0,
|
|
44
|
-
cursorCol: 0,
|
|
45
|
-
scrollOffset: 0,
|
|
46
|
-
isActive: false,
|
|
47
|
-
};
|
|
48
|
-
keypressHandler = null;
|
|
49
|
-
resizeHandler = null;
|
|
50
|
-
scrollRegionActive = false;
|
|
51
|
-
originalStdoutWrite = null;
|
|
52
|
-
outputIntercepted = false;
|
|
53
|
-
constructor(config = {}, writeStream = process.stdout, readStream = process.stdin) {
|
|
54
|
-
super();
|
|
55
|
-
this.writeStream = writeStream;
|
|
56
|
-
this.readStream = readStream;
|
|
57
|
-
this.config = {
|
|
58
|
-
visibleLines: config.visibleLines ?? 3,
|
|
59
|
-
maxLines: config.maxLines ?? 100,
|
|
60
|
-
prompt: config.prompt ?? '> ',
|
|
61
|
-
showLineNumbers: config.showLineNumbers ?? false,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Get terminal dimensions
|
|
66
|
-
*/
|
|
67
|
-
getTerminalSize() {
|
|
68
|
-
return {
|
|
69
|
-
rows: this.writeStream.rows || 24,
|
|
70
|
-
cols: this.writeStream.columns || 80,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Safely get a line from the buffer, returning empty string if undefined
|
|
75
|
-
*/
|
|
76
|
-
getLine(index) {
|
|
77
|
-
return this.state.lines[index] ?? '';
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Safely set a line in the buffer
|
|
81
|
-
*/
|
|
82
|
-
setLine(index, value) {
|
|
83
|
-
if (index >= 0 && index < this.state.lines.length) {
|
|
84
|
-
this.state.lines[index] = value;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Calculate the row where the chat box starts (from terminal top)
|
|
89
|
-
*/
|
|
90
|
-
getChatBoxStartRow() {
|
|
91
|
-
const { rows } = this.getTerminalSize();
|
|
92
|
-
// Chat box occupies bottom N lines: separator (1) + visible lines + prompt indicator
|
|
93
|
-
const boxHeight = 1 + this.config.visibleLines;
|
|
94
|
-
return Math.max(1, rows - boxHeight);
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Set up scroll region to protect the chat box area at bottom.
|
|
98
|
-
* All output written to stdout will scroll within the region above the chat box.
|
|
99
|
-
*/
|
|
100
|
-
setupScrollRegion() {
|
|
101
|
-
if (this.scrollRegionActive)
|
|
102
|
-
return;
|
|
103
|
-
const chatBoxStart = this.getChatBoxStartRow();
|
|
104
|
-
// Save cursor, set scroll region (line 1 to line above chat box), restore cursor
|
|
105
|
-
this.writeStream.write(ANSI.SAVE_CURSOR);
|
|
106
|
-
this.writeStream.write(ANSI.SET_SCROLL_REGION(1, chatBoxStart - 1));
|
|
107
|
-
this.writeStream.write(ANSI.RESTORE_CURSOR);
|
|
108
|
-
this.scrollRegionActive = true;
|
|
109
|
-
// Intercept stdout to ensure output goes to scroll region
|
|
110
|
-
this.interceptOutput();
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Reset scroll region to full terminal
|
|
114
|
-
*/
|
|
115
|
-
resetScrollRegion() {
|
|
116
|
-
if (!this.scrollRegionActive)
|
|
117
|
-
return;
|
|
118
|
-
this.writeStream.write(ANSI.SAVE_CURSOR);
|
|
119
|
-
this.writeStream.write(ANSI.RESET_SCROLL_REGION);
|
|
120
|
-
this.writeStream.write(ANSI.RESTORE_CURSOR);
|
|
121
|
-
this.scrollRegionActive = false;
|
|
122
|
-
this.restoreOutput();
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Intercept stdout to position output correctly in scroll region
|
|
126
|
-
*/
|
|
127
|
-
interceptOutput() {
|
|
128
|
-
if (this.outputIntercepted)
|
|
129
|
-
return;
|
|
130
|
-
this.originalStdoutWrite = this.writeStream.write.bind(this.writeStream);
|
|
131
|
-
const self = this;
|
|
132
|
-
const chatBoxStart = this.getChatBoxStartRow();
|
|
133
|
-
// Replace stdout.write to redirect output to scroll region
|
|
134
|
-
this.writeStream.write = function (chunk, encodingOrCallback, callback) {
|
|
135
|
-
if (!self.originalStdoutWrite || !self.state.isActive) {
|
|
136
|
-
if (self.originalStdoutWrite) {
|
|
137
|
-
if (typeof encodingOrCallback === 'function') {
|
|
138
|
-
return self.originalStdoutWrite(chunk, encodingOrCallback);
|
|
139
|
-
}
|
|
140
|
-
return self.originalStdoutWrite(chunk, encodingOrCallback, callback);
|
|
141
|
-
}
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
// Save cursor, move to output area, write, restore cursor to chat box
|
|
145
|
-
const output = typeof chunk === 'string' ? chunk : chunk.toString();
|
|
146
|
-
// Skip if it's just cursor positioning
|
|
147
|
-
if (/^\x1b\[\d*[ABCDGHJ]/.test(output) && output.length < 10) {
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
// Position at end of scroll region for output
|
|
151
|
-
self.originalStdoutWrite(ANSI.SAVE_CURSOR);
|
|
152
|
-
self.originalStdoutWrite(ANSI.CURSOR_TO(chatBoxStart - 1, 1));
|
|
153
|
-
// Write the actual content
|
|
154
|
-
let result;
|
|
155
|
-
if (typeof encodingOrCallback === 'function') {
|
|
156
|
-
result = self.originalStdoutWrite(chunk, encodingOrCallback);
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
result = self.originalStdoutWrite(chunk, encodingOrCallback, callback);
|
|
160
|
-
}
|
|
161
|
-
// Restore cursor to chat box
|
|
162
|
-
self.originalStdoutWrite(ANSI.RESTORE_CURSOR);
|
|
163
|
-
return result;
|
|
164
|
-
};
|
|
165
|
-
this.outputIntercepted = true;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Restore original stdout.write
|
|
169
|
-
*/
|
|
170
|
-
restoreOutput() {
|
|
171
|
-
if (!this.outputIntercepted || !this.originalStdoutWrite)
|
|
172
|
-
return;
|
|
173
|
-
this.writeStream.write = this.originalStdoutWrite;
|
|
174
|
-
this.originalStdoutWrite = null;
|
|
175
|
-
this.outputIntercepted = false;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Write to the output area (above the chat box)
|
|
179
|
-
* This is the proper way for other code to output while chat box is active
|
|
180
|
-
*/
|
|
181
|
-
writeToOutput(text) {
|
|
182
|
-
if (!this.state.isActive) {
|
|
183
|
-
this.writeStream.write(text);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const chatBoxStart = this.getChatBoxStartRow();
|
|
187
|
-
// Save cursor, move to scroll region, write, restore cursor
|
|
188
|
-
this.writeStream.write(ANSI.SAVE_CURSOR);
|
|
189
|
-
this.writeStream.write(ANSI.CURSOR_TO(chatBoxStart - 1, 1));
|
|
190
|
-
// Use original write if intercepted
|
|
191
|
-
if (this.originalStdoutWrite) {
|
|
192
|
-
this.originalStdoutWrite(text);
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
this.writeStream.write(text);
|
|
196
|
-
}
|
|
197
|
-
this.writeStream.write(ANSI.RESTORE_CURSOR);
|
|
198
|
-
this.render(); // Re-render chat box to ensure cursor position
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Activate the chat box - start listening for input
|
|
202
|
-
*/
|
|
203
|
-
activate() {
|
|
204
|
-
if (this.state.isActive)
|
|
205
|
-
return;
|
|
206
|
-
this.state.isActive = true;
|
|
207
|
-
// Set up raw mode for capturing all keypresses
|
|
208
|
-
if (this.readStream.isTTY) {
|
|
209
|
-
this.readStream.setRawMode(true);
|
|
210
|
-
}
|
|
211
|
-
readline.emitKeypressEvents(this.readStream);
|
|
212
|
-
// Handle keypresses
|
|
213
|
-
this.keypressHandler = (str, key) => this.handleKeypress(str, key);
|
|
214
|
-
this.readStream.on('keypress', this.keypressHandler);
|
|
215
|
-
// Handle terminal resize
|
|
216
|
-
this.resizeHandler = () => {
|
|
217
|
-
// On resize, need to reset and re-setup scroll region
|
|
218
|
-
if (this.scrollRegionActive) {
|
|
219
|
-
this.resetScrollRegion();
|
|
220
|
-
this.setupScrollRegion();
|
|
221
|
-
}
|
|
222
|
-
this.render();
|
|
223
|
-
};
|
|
224
|
-
this.writeStream.on('resize', this.resizeHandler);
|
|
225
|
-
// Clear screen and set up scroll region
|
|
226
|
-
this.clearAndSetup();
|
|
227
|
-
// Initial render
|
|
228
|
-
this.render();
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Clear screen and set up the chat box at bottom
|
|
232
|
-
*/
|
|
233
|
-
clearAndSetup() {
|
|
234
|
-
const chatBoxStart = this.getChatBoxStartRow();
|
|
235
|
-
// Position cursor at start of chat box area
|
|
236
|
-
this.writeStream.write(ANSI.CURSOR_TO(chatBoxStart, 1));
|
|
237
|
-
// Set up scroll region
|
|
238
|
-
this.setupScrollRegion();
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Deactivate the chat box - stop listening for input
|
|
242
|
-
*/
|
|
243
|
-
deactivate() {
|
|
244
|
-
if (!this.state.isActive)
|
|
245
|
-
return;
|
|
246
|
-
this.state.isActive = false;
|
|
247
|
-
// Reset scroll region
|
|
248
|
-
this.resetScrollRegion();
|
|
249
|
-
// Remove event handlers
|
|
250
|
-
if (this.keypressHandler) {
|
|
251
|
-
this.readStream.off('keypress', this.keypressHandler);
|
|
252
|
-
this.keypressHandler = null;
|
|
253
|
-
}
|
|
254
|
-
if (this.resizeHandler) {
|
|
255
|
-
this.writeStream.off('resize', this.resizeHandler);
|
|
256
|
-
this.resizeHandler = null;
|
|
257
|
-
}
|
|
258
|
-
// Restore raw mode
|
|
259
|
-
if (this.readStream.isTTY) {
|
|
260
|
-
this.readStream.setRawMode(false);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Handle keypress events
|
|
265
|
-
*/
|
|
266
|
-
handleKeypress(str, key) {
|
|
267
|
-
if (!key)
|
|
268
|
-
return;
|
|
269
|
-
// Handle special keys
|
|
270
|
-
if (key.ctrl) {
|
|
271
|
-
switch (key.name) {
|
|
272
|
-
case 'c':
|
|
273
|
-
this.emit('interrupt');
|
|
274
|
-
return;
|
|
275
|
-
case 'd':
|
|
276
|
-
if (this.getText().length === 0) {
|
|
277
|
-
this.emit('cancel');
|
|
278
|
-
}
|
|
279
|
-
return;
|
|
280
|
-
case 'a': // Home
|
|
281
|
-
this.cursorToLineStart();
|
|
282
|
-
break;
|
|
283
|
-
case 'e': // End
|
|
284
|
-
this.cursorToLineEnd();
|
|
285
|
-
break;
|
|
286
|
-
case 'k': // Kill to end of line
|
|
287
|
-
this.killToEndOfLine();
|
|
288
|
-
break;
|
|
289
|
-
case 'u': // Kill to start of line
|
|
290
|
-
this.killToStartOfLine();
|
|
291
|
-
break;
|
|
292
|
-
case 'w': // Delete word backward
|
|
293
|
-
this.deleteWordBackward();
|
|
294
|
-
break;
|
|
295
|
-
case 'left': // Word left
|
|
296
|
-
this.cursorWordLeft();
|
|
297
|
-
break;
|
|
298
|
-
case 'right': // Word right
|
|
299
|
-
this.cursorWordRight();
|
|
300
|
-
break;
|
|
301
|
-
case 'up': // Scroll up
|
|
302
|
-
this.scrollUp();
|
|
303
|
-
break;
|
|
304
|
-
case 'down': // Scroll down
|
|
305
|
-
this.scrollDown();
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
this.render();
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
if (key.meta) {
|
|
312
|
-
switch (key.name) {
|
|
313
|
-
case 'left': // Word left (alt+left)
|
|
314
|
-
this.cursorWordLeft();
|
|
315
|
-
break;
|
|
316
|
-
case 'right': // Word right (alt+right)
|
|
317
|
-
this.cursorWordRight();
|
|
318
|
-
break;
|
|
319
|
-
case 'backspace': // Delete word backward (alt+backspace)
|
|
320
|
-
this.deleteWordBackward();
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
this.render();
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
switch (key.name) {
|
|
327
|
-
case 'return':
|
|
328
|
-
if (key.shift) {
|
|
329
|
-
// Shift+Enter: insert newline
|
|
330
|
-
this.insertNewline();
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
// Enter: submit
|
|
334
|
-
this.submit();
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
break;
|
|
338
|
-
case 'backspace':
|
|
339
|
-
this.deleteBackward();
|
|
340
|
-
break;
|
|
341
|
-
case 'delete':
|
|
342
|
-
this.deleteForward();
|
|
343
|
-
break;
|
|
344
|
-
case 'up':
|
|
345
|
-
this.cursorUp();
|
|
346
|
-
break;
|
|
347
|
-
case 'down':
|
|
348
|
-
this.cursorDown();
|
|
349
|
-
break;
|
|
350
|
-
case 'left':
|
|
351
|
-
this.cursorLeft();
|
|
352
|
-
break;
|
|
353
|
-
case 'right':
|
|
354
|
-
this.cursorRight();
|
|
355
|
-
break;
|
|
356
|
-
case 'home':
|
|
357
|
-
this.cursorToLineStart();
|
|
358
|
-
break;
|
|
359
|
-
case 'end':
|
|
360
|
-
this.cursorToLineEnd();
|
|
361
|
-
break;
|
|
362
|
-
case 'pageup':
|
|
363
|
-
this.pageUp();
|
|
364
|
-
break;
|
|
365
|
-
case 'pagedown':
|
|
366
|
-
this.pageDown();
|
|
367
|
-
break;
|
|
368
|
-
case 'tab':
|
|
369
|
-
// Insert 2 spaces for tab
|
|
370
|
-
this.insertText(' ');
|
|
371
|
-
break;
|
|
372
|
-
case 'escape':
|
|
373
|
-
this.emit('cancel');
|
|
374
|
-
return;
|
|
375
|
-
default:
|
|
376
|
-
// Insert printable characters
|
|
377
|
-
if (str && str.length > 0 && !key.ctrl && !key.meta) {
|
|
378
|
-
this.insertText(str);
|
|
379
|
-
}
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
this.render();
|
|
383
|
-
this.emit('change', this.getText());
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Insert text at cursor position
|
|
387
|
-
*/
|
|
388
|
-
insertText(text) {
|
|
389
|
-
const { cursorRow, cursorCol, lines } = this.state;
|
|
390
|
-
const line = this.getLine(cursorRow);
|
|
391
|
-
// Handle multi-character paste (may contain newlines)
|
|
392
|
-
const parts = text.split('\n');
|
|
393
|
-
if (parts.length === 1) {
|
|
394
|
-
// Single line insert
|
|
395
|
-
this.setLine(cursorRow, line.slice(0, cursorCol) + text + line.slice(cursorCol));
|
|
396
|
-
this.state.cursorCol += text.length;
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
// Multi-line paste
|
|
400
|
-
const before = line.slice(0, cursorCol);
|
|
401
|
-
const after = line.slice(cursorCol);
|
|
402
|
-
const firstPart = parts[0] ?? '';
|
|
403
|
-
this.setLine(cursorRow, before + firstPart);
|
|
404
|
-
for (let i = 1; i < parts.length; i++) {
|
|
405
|
-
lines.splice(cursorRow + i, 0, parts[i] ?? '');
|
|
406
|
-
}
|
|
407
|
-
const lastIdx = cursorRow + parts.length - 1;
|
|
408
|
-
this.setLine(lastIdx, this.getLine(lastIdx) + after);
|
|
409
|
-
this.state.cursorRow += parts.length - 1;
|
|
410
|
-
const lastPart = parts[parts.length - 1];
|
|
411
|
-
this.state.cursorCol = lastPart?.length ?? 0;
|
|
412
|
-
}
|
|
413
|
-
// Enforce max lines
|
|
414
|
-
while (lines.length > this.config.maxLines) {
|
|
415
|
-
lines.shift();
|
|
416
|
-
if (this.state.cursorRow > 0)
|
|
417
|
-
this.state.cursorRow--;
|
|
418
|
-
}
|
|
419
|
-
this.ensureCursorVisible();
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Insert a newline at cursor position
|
|
423
|
-
*/
|
|
424
|
-
insertNewline() {
|
|
425
|
-
const { cursorRow, cursorCol, lines } = this.state;
|
|
426
|
-
const line = this.getLine(cursorRow);
|
|
427
|
-
const before = line.slice(0, cursorCol);
|
|
428
|
-
const after = line.slice(cursorCol);
|
|
429
|
-
this.setLine(cursorRow, before);
|
|
430
|
-
lines.splice(cursorRow + 1, 0, after);
|
|
431
|
-
this.state.cursorRow++;
|
|
432
|
-
this.state.cursorCol = 0;
|
|
433
|
-
// Enforce max lines
|
|
434
|
-
if (lines.length > this.config.maxLines) {
|
|
435
|
-
lines.shift();
|
|
436
|
-
this.state.cursorRow--;
|
|
437
|
-
}
|
|
438
|
-
this.ensureCursorVisible();
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Delete character before cursor
|
|
442
|
-
*/
|
|
443
|
-
deleteBackward() {
|
|
444
|
-
const { cursorRow, cursorCol, lines } = this.state;
|
|
445
|
-
const currentLine = this.getLine(cursorRow);
|
|
446
|
-
if (cursorCol > 0) {
|
|
447
|
-
// Delete within line
|
|
448
|
-
this.setLine(cursorRow, currentLine.slice(0, cursorCol - 1) + currentLine.slice(cursorCol));
|
|
449
|
-
this.state.cursorCol--;
|
|
450
|
-
}
|
|
451
|
-
else if (cursorRow > 0) {
|
|
452
|
-
// Join with previous line
|
|
453
|
-
const prevLine = this.getLine(cursorRow - 1);
|
|
454
|
-
this.setLine(cursorRow - 1, prevLine + currentLine);
|
|
455
|
-
lines.splice(cursorRow, 1);
|
|
456
|
-
this.state.cursorRow--;
|
|
457
|
-
this.state.cursorCol = prevLine.length;
|
|
458
|
-
}
|
|
459
|
-
this.ensureCursorVisible();
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Delete character after cursor
|
|
463
|
-
*/
|
|
464
|
-
deleteForward() {
|
|
465
|
-
const { cursorRow, cursorCol, lines } = this.state;
|
|
466
|
-
const line = this.getLine(cursorRow);
|
|
467
|
-
if (cursorCol < line.length) {
|
|
468
|
-
// Delete within line
|
|
469
|
-
this.setLine(cursorRow, line.slice(0, cursorCol) + line.slice(cursorCol + 1));
|
|
470
|
-
}
|
|
471
|
-
else if (cursorRow < lines.length - 1) {
|
|
472
|
-
// Join with next line
|
|
473
|
-
this.setLine(cursorRow, line + this.getLine(cursorRow + 1));
|
|
474
|
-
lines.splice(cursorRow + 1, 1);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* Delete word backward
|
|
479
|
-
*/
|
|
480
|
-
deleteWordBackward() {
|
|
481
|
-
const { cursorRow, cursorCol } = this.state;
|
|
482
|
-
const line = this.getLine(cursorRow);
|
|
483
|
-
if (cursorCol === 0) {
|
|
484
|
-
if (cursorRow > 0) {
|
|
485
|
-
this.deleteBackward();
|
|
486
|
-
}
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
// Find word boundary
|
|
490
|
-
let pos = cursorCol;
|
|
491
|
-
// Skip spaces
|
|
492
|
-
while (pos > 0 && line.charAt(pos - 1) === ' ')
|
|
493
|
-
pos--;
|
|
494
|
-
// Skip word
|
|
495
|
-
while (pos > 0 && line.charAt(pos - 1) !== ' ')
|
|
496
|
-
pos--;
|
|
497
|
-
this.setLine(cursorRow, line.slice(0, pos) + line.slice(cursorCol));
|
|
498
|
-
this.state.cursorCol = pos;
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Kill (delete) to end of line
|
|
502
|
-
*/
|
|
503
|
-
killToEndOfLine() {
|
|
504
|
-
const { cursorRow, cursorCol } = this.state;
|
|
505
|
-
this.setLine(cursorRow, this.getLine(cursorRow).slice(0, cursorCol));
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Kill (delete) to start of line
|
|
509
|
-
*/
|
|
510
|
-
killToStartOfLine() {
|
|
511
|
-
const { cursorRow, cursorCol } = this.state;
|
|
512
|
-
this.setLine(cursorRow, this.getLine(cursorRow).slice(cursorCol));
|
|
513
|
-
this.state.cursorCol = 0;
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Move cursor left
|
|
517
|
-
*/
|
|
518
|
-
cursorLeft() {
|
|
519
|
-
if (this.state.cursorCol > 0) {
|
|
520
|
-
this.state.cursorCol--;
|
|
521
|
-
}
|
|
522
|
-
else if (this.state.cursorRow > 0) {
|
|
523
|
-
this.state.cursorRow--;
|
|
524
|
-
this.state.cursorCol = this.getLine(this.state.cursorRow).length;
|
|
525
|
-
}
|
|
526
|
-
this.ensureCursorVisible();
|
|
527
|
-
}
|
|
528
|
-
/**
|
|
529
|
-
* Move cursor right
|
|
530
|
-
*/
|
|
531
|
-
cursorRight() {
|
|
532
|
-
const line = this.getLine(this.state.cursorRow);
|
|
533
|
-
if (this.state.cursorCol < line.length) {
|
|
534
|
-
this.state.cursorCol++;
|
|
535
|
-
}
|
|
536
|
-
else if (this.state.cursorRow < this.state.lines.length - 1) {
|
|
537
|
-
this.state.cursorRow++;
|
|
538
|
-
this.state.cursorCol = 0;
|
|
539
|
-
}
|
|
540
|
-
this.ensureCursorVisible();
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Move cursor up
|
|
544
|
-
*/
|
|
545
|
-
cursorUp() {
|
|
546
|
-
if (this.state.cursorRow > 0) {
|
|
547
|
-
this.state.cursorRow--;
|
|
548
|
-
// Clamp column to line length
|
|
549
|
-
this.state.cursorCol = Math.min(this.state.cursorCol, this.getLine(this.state.cursorRow).length);
|
|
550
|
-
}
|
|
551
|
-
this.ensureCursorVisible();
|
|
552
|
-
}
|
|
553
|
-
/**
|
|
554
|
-
* Move cursor down
|
|
555
|
-
*/
|
|
556
|
-
cursorDown() {
|
|
557
|
-
if (this.state.cursorRow < this.state.lines.length - 1) {
|
|
558
|
-
this.state.cursorRow++;
|
|
559
|
-
// Clamp column to line length
|
|
560
|
-
this.state.cursorCol = Math.min(this.state.cursorCol, this.getLine(this.state.cursorRow).length);
|
|
561
|
-
}
|
|
562
|
-
this.ensureCursorVisible();
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* Move cursor to start of line
|
|
566
|
-
*/
|
|
567
|
-
cursorToLineStart() {
|
|
568
|
-
this.state.cursorCol = 0;
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Move cursor to end of line
|
|
572
|
-
*/
|
|
573
|
-
cursorToLineEnd() {
|
|
574
|
-
this.state.cursorCol = this.getLine(this.state.cursorRow).length;
|
|
575
|
-
}
|
|
576
|
-
/**
|
|
577
|
-
* Move cursor one word left
|
|
578
|
-
*/
|
|
579
|
-
cursorWordLeft() {
|
|
580
|
-
const { cursorRow, cursorCol } = this.state;
|
|
581
|
-
const line = this.getLine(cursorRow);
|
|
582
|
-
if (cursorCol === 0) {
|
|
583
|
-
if (cursorRow > 0) {
|
|
584
|
-
this.state.cursorRow--;
|
|
585
|
-
this.state.cursorCol = this.getLine(this.state.cursorRow).length;
|
|
586
|
-
}
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
let pos = cursorCol;
|
|
590
|
-
// Skip spaces
|
|
591
|
-
while (pos > 0 && line.charAt(pos - 1) === ' ')
|
|
592
|
-
pos--;
|
|
593
|
-
// Skip word
|
|
594
|
-
while (pos > 0 && line.charAt(pos - 1) !== ' ')
|
|
595
|
-
pos--;
|
|
596
|
-
this.state.cursorCol = pos;
|
|
597
|
-
this.ensureCursorVisible();
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Move cursor one word right
|
|
601
|
-
*/
|
|
602
|
-
cursorWordRight() {
|
|
603
|
-
const { cursorRow, cursorCol, lines } = this.state;
|
|
604
|
-
const line = this.getLine(cursorRow);
|
|
605
|
-
if (cursorCol >= line.length) {
|
|
606
|
-
if (cursorRow < lines.length - 1) {
|
|
607
|
-
this.state.cursorRow++;
|
|
608
|
-
this.state.cursorCol = 0;
|
|
609
|
-
}
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
let pos = cursorCol;
|
|
613
|
-
// Skip word
|
|
614
|
-
while (pos < line.length && line.charAt(pos) !== ' ')
|
|
615
|
-
pos++;
|
|
616
|
-
// Skip spaces
|
|
617
|
-
while (pos < line.length && line.charAt(pos) === ' ')
|
|
618
|
-
pos++;
|
|
619
|
-
this.state.cursorCol = pos;
|
|
620
|
-
this.ensureCursorVisible();
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Page up
|
|
624
|
-
*/
|
|
625
|
-
pageUp() {
|
|
626
|
-
const linesToMove = this.config.visibleLines;
|
|
627
|
-
this.state.cursorRow = Math.max(0, this.state.cursorRow - linesToMove);
|
|
628
|
-
this.state.cursorCol = Math.min(this.state.cursorCol, this.getLine(this.state.cursorRow).length);
|
|
629
|
-
this.ensureCursorVisible();
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Page down
|
|
633
|
-
*/
|
|
634
|
-
pageDown() {
|
|
635
|
-
const linesToMove = this.config.visibleLines;
|
|
636
|
-
this.state.cursorRow = Math.min(this.state.lines.length - 1, this.state.cursorRow + linesToMove);
|
|
637
|
-
this.state.cursorCol = Math.min(this.state.cursorCol, this.getLine(this.state.cursorRow).length);
|
|
638
|
-
this.ensureCursorVisible();
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Scroll up (without moving cursor)
|
|
642
|
-
*/
|
|
643
|
-
scrollUp() {
|
|
644
|
-
if (this.state.scrollOffset > 0) {
|
|
645
|
-
this.state.scrollOffset--;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Scroll down (without moving cursor)
|
|
650
|
-
*/
|
|
651
|
-
scrollDown() {
|
|
652
|
-
const maxScroll = Math.max(0, this.state.lines.length - this.config.visibleLines);
|
|
653
|
-
if (this.state.scrollOffset < maxScroll) {
|
|
654
|
-
this.state.scrollOffset++;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Ensure cursor is visible (adjust scroll if needed)
|
|
659
|
-
*/
|
|
660
|
-
ensureCursorVisible() {
|
|
661
|
-
const { cursorRow, scrollOffset } = this.state;
|
|
662
|
-
const { visibleLines } = this.config;
|
|
663
|
-
// Scroll up if cursor is above visible area
|
|
664
|
-
if (cursorRow < scrollOffset) {
|
|
665
|
-
this.state.scrollOffset = cursorRow;
|
|
666
|
-
}
|
|
667
|
-
// Scroll down if cursor is below visible area
|
|
668
|
-
if (cursorRow >= scrollOffset + visibleLines) {
|
|
669
|
-
this.state.scrollOffset = cursorRow - visibleLines + 1;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* Submit the current content
|
|
674
|
-
*/
|
|
675
|
-
submit() {
|
|
676
|
-
const text = this.getText();
|
|
677
|
-
this.clear();
|
|
678
|
-
this.emit('submit', text);
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Get the full text content
|
|
682
|
-
*/
|
|
683
|
-
getText() {
|
|
684
|
-
return this.state.lines.join('\n');
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Set the text content
|
|
688
|
-
*/
|
|
689
|
-
setText(text) {
|
|
690
|
-
this.state.lines = text.split('\n');
|
|
691
|
-
if (this.state.lines.length === 0) {
|
|
692
|
-
this.state.lines = [''];
|
|
693
|
-
}
|
|
694
|
-
this.state.cursorRow = this.state.lines.length - 1;
|
|
695
|
-
this.state.cursorCol = this.getLine(this.state.cursorRow).length;
|
|
696
|
-
this.state.scrollOffset = 0;
|
|
697
|
-
this.ensureCursorVisible();
|
|
698
|
-
this.render();
|
|
699
|
-
}
|
|
700
|
-
/**
|
|
701
|
-
* Clear the content
|
|
702
|
-
*/
|
|
703
|
-
clear() {
|
|
704
|
-
this.state.lines = [''];
|
|
705
|
-
this.state.cursorRow = 0;
|
|
706
|
-
this.state.cursorCol = 0;
|
|
707
|
-
this.state.scrollOffset = 0;
|
|
708
|
-
this.render();
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* Render the chat box
|
|
712
|
-
*/
|
|
713
|
-
render() {
|
|
714
|
-
if (!this.state.isActive)
|
|
715
|
-
return;
|
|
716
|
-
const { cols } = this.getTerminalSize();
|
|
717
|
-
const startRow = this.getChatBoxStartRow();
|
|
718
|
-
const { lines, cursorRow, cursorCol, scrollOffset } = this.state;
|
|
719
|
-
const { visibleLines, prompt } = this.config;
|
|
720
|
-
// Hide cursor during render
|
|
721
|
-
this.writeStream.write(ANSI.HIDE_CURSOR);
|
|
722
|
-
// Move to start of chat box area
|
|
723
|
-
this.writeStream.write(ANSI.CURSOR_TO(startRow, 1));
|
|
724
|
-
// Draw separator line
|
|
725
|
-
const separatorWidth = Math.min(cols - 2, 55);
|
|
726
|
-
this.writeStream.write(ANSI.CLEAR_LINE);
|
|
727
|
-
this.writeStream.write(theme.ui.border('─'.repeat(separatorWidth)));
|
|
728
|
-
// Draw visible lines
|
|
729
|
-
const promptWidth = prompt.length;
|
|
730
|
-
const contentWidth = cols - promptWidth - 1;
|
|
731
|
-
for (let i = 0; i < visibleLines; i++) {
|
|
732
|
-
const lineIndex = scrollOffset + i;
|
|
733
|
-
const lineContent = lineIndex < lines.length ? (lines[lineIndex] ?? '') : '';
|
|
734
|
-
this.writeStream.write(ANSI.CURSOR_TO(startRow + 1 + i, 1));
|
|
735
|
-
this.writeStream.write(ANSI.CLEAR_LINE);
|
|
736
|
-
// Draw prompt for first visible line or continuation indicator
|
|
737
|
-
if (i === 0 && scrollOffset === 0) {
|
|
738
|
-
this.writeStream.write(theme.user(prompt));
|
|
739
|
-
}
|
|
740
|
-
else if (lineIndex < lines.length) {
|
|
741
|
-
// Continuation line
|
|
742
|
-
this.writeStream.write(theme.ui.muted(' '));
|
|
743
|
-
}
|
|
744
|
-
else {
|
|
745
|
-
// Empty line
|
|
746
|
-
this.writeStream.write(' ');
|
|
747
|
-
}
|
|
748
|
-
// Draw line content (truncate if needed)
|
|
749
|
-
let displayContent = lineContent;
|
|
750
|
-
if (displayContent.length > contentWidth) {
|
|
751
|
-
displayContent = displayContent.slice(0, contentWidth - 1) + '…';
|
|
752
|
-
}
|
|
753
|
-
this.writeStream.write(displayContent);
|
|
754
|
-
}
|
|
755
|
-
// Calculate actual cursor position on screen
|
|
756
|
-
const cursorScreenRow = startRow + 1 + (cursorRow - scrollOffset);
|
|
757
|
-
const cursorScreenCol = promptWidth + cursorCol + 1;
|
|
758
|
-
// Position cursor
|
|
759
|
-
this.writeStream.write(ANSI.CURSOR_TO(cursorScreenRow, Math.min(cursorScreenCol, cols)));
|
|
760
|
-
// Show cursor
|
|
761
|
-
this.writeStream.write(ANSI.SHOW_CURSOR);
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* Clear the chat box area from screen
|
|
765
|
-
*/
|
|
766
|
-
clearScreen() {
|
|
767
|
-
const startRow = this.getChatBoxStartRow();
|
|
768
|
-
const { visibleLines } = this.config;
|
|
769
|
-
for (let i = 0; i <= visibleLines; i++) {
|
|
770
|
-
this.writeStream.write(ANSI.CURSOR_TO(startRow + i, 1));
|
|
771
|
-
this.writeStream.write(ANSI.CLEAR_LINE);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* Dispose and cleanup
|
|
776
|
-
*/
|
|
777
|
-
dispose() {
|
|
778
|
-
this.deactivate();
|
|
779
|
-
this.clearScreen();
|
|
780
|
-
this.restoreOutput();
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Check if the chat box is currently active
|
|
784
|
-
*/
|
|
785
|
-
isActive() {
|
|
786
|
-
return this.state.isActive;
|
|
787
|
-
}
|
|
788
|
-
/**
|
|
789
|
-
* Get current line count
|
|
790
|
-
*/
|
|
791
|
-
getLineCount() {
|
|
792
|
-
return this.state.lines.length;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* Singleton instance
|
|
797
|
-
*/
|
|
798
|
-
let globalChatBox = null;
|
|
799
|
-
export function getChatBox(config) {
|
|
800
|
-
if (!globalChatBox) {
|
|
801
|
-
globalChatBox = new ChatBox(config);
|
|
802
|
-
}
|
|
803
|
-
return globalChatBox;
|
|
804
|
-
}
|
|
805
|
-
export function resetChatBox() {
|
|
806
|
-
if (globalChatBox) {
|
|
807
|
-
globalChatBox.dispose();
|
|
808
|
-
globalChatBox = null;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
//# sourceMappingURL=chatBox.js.map
|