agent-mp 0.5.22 → 0.5.24
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/commands/repl.d.ts +0 -2
- package/dist/commands/repl.js +187 -227
- package/dist/commands/setup.js +55 -0
- package/dist/core/engine.js +24 -31
- package/dist/index.js +26 -19
- package/dist/ui/input.d.ts +23 -25
- package/dist/ui/input.js +310 -248
- package/dist/utils/qwen-auth.d.ts +14 -4
- package/dist/utils/qwen-auth.js +81 -8
- package/package.json +1 -1
package/dist/ui/input.js
CHANGED
|
@@ -1,347 +1,409 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// Active: 10 = 7 (activity box) + 3 (input box: 1 content + 2 borders)
|
|
14
|
-
// The scroll region is updated whenever activity mode toggles.
|
|
15
|
-
const IDLE_RESERVED = MAX_CONTENT_ROWS + 3; // 7
|
|
16
|
-
const ACTIVE_RESERVED = ACTIVITY_LINES + 2 + 3; // 10 = activity(7) + input(3)
|
|
17
|
-
// ─── FixedInput ──────────────────────────────────────────────────────────────
|
|
2
|
+
const DIM = chalk.dim;
|
|
3
|
+
const B = (s) => chalk.rgb(30, 110, 185)(s);
|
|
4
|
+
const PROMPT = B('> ');
|
|
5
|
+
const PROMPT_W = 2;
|
|
6
|
+
const INDENT = ' ';
|
|
7
|
+
const PLACEHOLDER = 'Type your message… (Shift+Enter / \\ para nueva línea)';
|
|
8
|
+
/**
|
|
9
|
+
* Inline prompt, styled like Gemini/Qwen CLI.
|
|
10
|
+
* Draws at the current cursor line; when output arrives we erase the area
|
|
11
|
+
* (line-by-line, \x1b[2K) and redraw below. No absolute positioning.
|
|
12
|
+
*/
|
|
18
13
|
export class FixedInput {
|
|
19
|
-
buf = '';
|
|
20
14
|
history = [];
|
|
21
|
-
histIdx = -1;
|
|
22
15
|
origLog;
|
|
23
|
-
_pasting = false;
|
|
24
|
-
_pasteAccum = '';
|
|
25
|
-
_drawPending = false;
|
|
26
|
-
// ── Activity box state (null = input mode, string = activity mode) ──────────
|
|
27
16
|
_activityHeader = null;
|
|
28
17
|
_activityLines = [];
|
|
29
|
-
|
|
18
|
+
_inputBuffer = [];
|
|
19
|
+
_cursorPos = 0;
|
|
20
|
+
_pasting = false;
|
|
21
|
+
_pasteAccum = '';
|
|
22
|
+
_resolveInput;
|
|
23
|
+
_inputActive = false;
|
|
24
|
+
// Geometry after the last draw.
|
|
25
|
+
_areaRows = 0; // total rows the area occupies
|
|
26
|
+
_cursorRow = 0; // row (0-indexed from top of area) where terminal cursor sits
|
|
27
|
+
_onResize;
|
|
30
28
|
get cols() { return process.stdout.columns || 80; }
|
|
31
|
-
get _reservedRows() { return this._activityHeader !== null ? ACTIVE_RESERVED : IDLE_RESERVED; }
|
|
32
|
-
get scrollBottom() { return this.rows - this._reservedRows; }
|
|
33
|
-
_contentRows() {
|
|
34
|
-
// During activity mode only 1 content row fits below the activity box
|
|
35
|
-
if (this._activityHeader !== null)
|
|
36
|
-
return 1;
|
|
37
|
-
const w = this.cols - PREFIX_COLS - 2;
|
|
38
|
-
if (w <= 0)
|
|
39
|
-
return 1;
|
|
40
|
-
if (!this.buf)
|
|
41
|
-
return 1;
|
|
42
|
-
let n = 0;
|
|
43
|
-
for (const seg of this.buf.split('\n'))
|
|
44
|
-
n += Math.max(1, Math.ceil((seg.length || 1) / w));
|
|
45
|
-
return Math.min(n, MAX_CONTENT_ROWS);
|
|
46
|
-
}
|
|
47
|
-
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
48
29
|
setup() {
|
|
49
30
|
this.origLog = console.log;
|
|
50
31
|
console.log = (...args) => {
|
|
51
|
-
const text = args.map(a =>
|
|
32
|
+
const text = args.map(a => typeof a === 'string' ? a : String(a)).join(' ');
|
|
52
33
|
this.println(text);
|
|
53
34
|
};
|
|
54
|
-
this._setScrollRegion();
|
|
55
|
-
process.stdout.write(`\x1b[${this.scrollBottom};1H`);
|
|
56
|
-
this._clearReserved();
|
|
57
|
-
this._drawBox();
|
|
58
35
|
process.stdout.write('\x1b[?2004h');
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this._clearReserved();
|
|
62
|
-
this._drawBox();
|
|
63
|
-
});
|
|
36
|
+
this._onResize = () => this._redraw();
|
|
37
|
+
process.stdout.on('resize', this._onResize);
|
|
64
38
|
}
|
|
65
39
|
teardown() {
|
|
66
|
-
this._activityHeader = null;
|
|
67
|
-
this._activityLines = [];
|
|
68
40
|
console.log = this.origLog;
|
|
69
41
|
process.stdout.write('\x1b[?2004l');
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
process.stdout.write(`\x1b[${this.rows};1H\n`);
|
|
42
|
+
if (this._onResize)
|
|
43
|
+
process.stdout.off('resize', this._onResize);
|
|
73
44
|
}
|
|
74
|
-
redrawBox() { this._drawBox(); }
|
|
75
45
|
suspend() {
|
|
46
|
+
this._clearArea();
|
|
76
47
|
console.log = this.origLog;
|
|
77
48
|
process.stdout.write('\x1b[?2004l');
|
|
78
|
-
process.stdout.write('\x1b[r');
|
|
79
|
-
this._clearReserved();
|
|
80
|
-
process.stdout.write(`\x1b[${this.scrollBottom};1H`);
|
|
81
49
|
return () => {
|
|
82
50
|
console.log = (...args) => {
|
|
83
|
-
const text = args.map(a =>
|
|
51
|
+
const text = args.map(a => typeof a === 'string' ? a : String(a)).join(' ');
|
|
84
52
|
this.println(text);
|
|
85
53
|
};
|
|
86
|
-
this._setScrollRegion();
|
|
87
|
-
this._clearReserved();
|
|
88
|
-
this._drawBox();
|
|
89
54
|
process.stdout.write('\x1b[?2004h');
|
|
55
|
+
this._redraw();
|
|
90
56
|
};
|
|
91
57
|
}
|
|
92
|
-
// ── Activity box API ───────────────────────────────────────────────────────
|
|
93
|
-
/** Enter activity mode: show the 5-line log box instead of the input box. */
|
|
94
58
|
startActivity(header) {
|
|
95
59
|
this._activityHeader = header;
|
|
96
60
|
this._activityLines = [];
|
|
97
|
-
this.
|
|
98
|
-
this._drawBox();
|
|
61
|
+
this._redraw();
|
|
99
62
|
}
|
|
100
|
-
/** Update the header line (spinner frame + elapsed time) without clearing lines. */
|
|
101
63
|
updateActivityHeader(header) {
|
|
102
64
|
this._activityHeader = header;
|
|
103
|
-
this.
|
|
65
|
+
this._redraw();
|
|
104
66
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Append a line to the activity log (keeps last ACTIVITY_LINES lines).
|
|
107
|
-
* Strips ANSI codes and skips blank or pure-JSON lines.
|
|
108
|
-
*/
|
|
109
67
|
pushActivity(rawLine) {
|
|
110
68
|
if (this._activityHeader === null)
|
|
111
69
|
return;
|
|
112
|
-
|
|
113
|
-
const clean = rawLine
|
|
114
|
-
.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '')
|
|
115
|
-
.replace(/[^\x20-\x7e\u00a0-\uffff]/g, '')
|
|
116
|
-
.trim();
|
|
70
|
+
const clean = rawLine.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').trim();
|
|
117
71
|
if (!clean)
|
|
118
72
|
return;
|
|
119
73
|
this._activityLines.push(clean);
|
|
120
|
-
if (this._activityLines.length >
|
|
74
|
+
if (this._activityLines.length > 5)
|
|
121
75
|
this._activityLines.shift();
|
|
122
|
-
this.
|
|
76
|
+
this._redraw();
|
|
77
|
+
}
|
|
78
|
+
/** Replace all content lines at once (for streaming preview). */
|
|
79
|
+
setActivityLines(lines) {
|
|
80
|
+
if (this._activityHeader === null)
|
|
81
|
+
return;
|
|
82
|
+
this._activityLines = lines.map(l => l.slice(0, this.cols - 4));
|
|
83
|
+
this._redraw();
|
|
123
84
|
}
|
|
124
|
-
/** Leave activity mode and restore the normal input box. */
|
|
125
85
|
stopActivity() {
|
|
126
|
-
// Explicitly clear the full ACTIVE reserved zone before shrinking
|
|
127
|
-
// the scroll region — otherwise activity box rows bleed into scroll history.
|
|
128
|
-
const activeSB = this.rows - ACTIVE_RESERVED;
|
|
129
|
-
for (let r = activeSB + 1; r <= this.rows; r++)
|
|
130
|
-
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
131
86
|
this._activityHeader = null;
|
|
132
87
|
this._activityLines = [];
|
|
133
|
-
this.
|
|
134
|
-
|
|
88
|
+
this._redraw();
|
|
89
|
+
}
|
|
90
|
+
println(text) {
|
|
91
|
+
this._clearArea();
|
|
92
|
+
process.stdout.write(text + '\n');
|
|
93
|
+
this._redraw();
|
|
94
|
+
}
|
|
95
|
+
printSeparator() {
|
|
96
|
+
this._clearArea();
|
|
97
|
+
process.stdout.write(DIM('─'.repeat(this.cols - 1)) + '\n');
|
|
98
|
+
this._redraw();
|
|
99
|
+
}
|
|
100
|
+
redrawBox() {
|
|
101
|
+
this._redraw();
|
|
135
102
|
}
|
|
136
|
-
// ── Input ──────────────────────────────────────────────────────────────────
|
|
137
103
|
readLine() {
|
|
138
|
-
this.
|
|
139
|
-
this.
|
|
140
|
-
this.
|
|
141
|
-
|
|
104
|
+
this._inputBuffer = [];
|
|
105
|
+
this._cursorPos = 0;
|
|
106
|
+
this._inputActive = true;
|
|
107
|
+
this._redraw();
|
|
108
|
+
if (process.stdin.isTTY)
|
|
142
109
|
process.stdin.setRawMode(true);
|
|
143
|
-
|
|
144
|
-
|
|
110
|
+
process.stdin.resume();
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
this._resolveInput = resolve;
|
|
113
|
+
const finish = (value) => {
|
|
114
|
+
this._inputActive = false;
|
|
115
|
+
this._clearArea();
|
|
145
116
|
process.stdin.removeListener('data', onData);
|
|
146
117
|
if (process.stdin.isTTY)
|
|
147
118
|
process.stdin.setRawMode(false);
|
|
148
|
-
|
|
149
|
-
this.
|
|
150
|
-
|
|
119
|
+
process.stdin.pause();
|
|
120
|
+
const r = this._resolveInput;
|
|
121
|
+
this._resolveInput = undefined;
|
|
122
|
+
r?.(value);
|
|
151
123
|
};
|
|
152
124
|
const onData = (data) => {
|
|
153
|
-
const hex = data.toString('hex');
|
|
154
125
|
const key = data.toString();
|
|
155
|
-
|
|
156
|
-
|
|
126
|
+
const hex = data.toString('hex');
|
|
127
|
+
// Bracketed paste start (may include end marker in same chunk).
|
|
128
|
+
if (!this._pasting && key.includes('\x1b[200~')) {
|
|
157
129
|
this._pasting = true;
|
|
158
130
|
this._pasteAccum = '';
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
131
|
+
const afterStart = key.slice(key.indexOf('\x1b[200~') + 6);
|
|
132
|
+
const endIdx = afterStart.indexOf('\x1b[201~');
|
|
133
|
+
if (endIdx !== -1) {
|
|
134
|
+
this._pasteAccum += afterStart.slice(0, endIdx);
|
|
135
|
+
this._commitPaste();
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this._pasteAccum += afterStart;
|
|
139
|
+
}
|
|
162
140
|
return;
|
|
163
141
|
}
|
|
164
|
-
// ── Bracketed paste: accumulate ───────────────────────────────
|
|
165
142
|
if (this._pasting) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
this._pasteAccum +=
|
|
169
|
-
this.
|
|
170
|
-
this._pasting = false;
|
|
171
|
-
this._pasteAccum = '';
|
|
172
|
-
this._scheduleDraw();
|
|
143
|
+
const endIdx = key.indexOf('\x1b[201~');
|
|
144
|
+
if (endIdx !== -1) {
|
|
145
|
+
this._pasteAccum += key.slice(0, endIdx);
|
|
146
|
+
this._commitPaste();
|
|
173
147
|
}
|
|
174
148
|
else {
|
|
175
149
|
this._pasteAccum += key;
|
|
176
150
|
}
|
|
177
151
|
return;
|
|
178
152
|
}
|
|
179
|
-
//
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
hex === '1b5b31333b327e' ||
|
|
183
|
-
hex === '1b5b31333b3275' ||
|
|
184
|
-
hex === '1b4f4d') {
|
|
185
|
-
this.buf += '\n';
|
|
186
|
-
this._scheduleDraw();
|
|
187
|
-
// ── Enter → submit ───────────────────────────────────────────
|
|
188
|
-
}
|
|
189
|
-
else if (key === '\r') {
|
|
190
|
-
const line = this.buf;
|
|
153
|
+
// Enter — submit
|
|
154
|
+
if (key === '\r') {
|
|
155
|
+
const line = this._inputBuffer.join('');
|
|
191
156
|
if (line.trim()) {
|
|
192
157
|
this.history.unshift(line);
|
|
193
158
|
if (this.history.length > 200)
|
|
194
159
|
this.history.pop();
|
|
195
160
|
}
|
|
196
|
-
|
|
161
|
+
finish(line);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// Ctrl+J (LF) — insert newline
|
|
165
|
+
if (key === '\n') {
|
|
166
|
+
this._inputBuffer.splice(this._cursorPos, 0, '\n');
|
|
167
|
+
this._cursorPos++;
|
|
168
|
+
this._redraw();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Backspace
|
|
172
|
+
if (key === '\x7f' || key === '\x08') {
|
|
173
|
+
if (this._cursorPos > 0) {
|
|
174
|
+
this._inputBuffer.splice(this._cursorPos - 1, 1);
|
|
175
|
+
this._cursorPos--;
|
|
176
|
+
this._redraw();
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
197
179
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.
|
|
180
|
+
// Delete
|
|
181
|
+
if (hex === '1b5b337e') {
|
|
182
|
+
if (this._cursorPos < this._inputBuffer.length) {
|
|
183
|
+
this._inputBuffer.splice(this._cursorPos, 1);
|
|
184
|
+
this._redraw();
|
|
202
185
|
}
|
|
186
|
+
return;
|
|
203
187
|
}
|
|
204
|
-
|
|
188
|
+
// Ctrl+C
|
|
189
|
+
if (key === '\x03') {
|
|
190
|
+
this._clearArea();
|
|
191
|
+
if (process.stdin.isTTY)
|
|
192
|
+
process.stdin.setRawMode(false);
|
|
193
|
+
process.stdin.pause();
|
|
205
194
|
this.teardown();
|
|
206
195
|
process.exit(0);
|
|
207
196
|
}
|
|
208
|
-
|
|
209
|
-
|
|
197
|
+
// Ctrl+D
|
|
198
|
+
if (key === '\x04') {
|
|
199
|
+
finish('/exit');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Ctrl+U — clear line
|
|
203
|
+
if (key === '\x15') {
|
|
204
|
+
this._inputBuffer = [];
|
|
205
|
+
this._cursorPos = 0;
|
|
206
|
+
this._redraw();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Ctrl+W / Alt+Backspace — delete prev word
|
|
210
|
+
if (key === '\x17' || hex === '1b7f') {
|
|
211
|
+
const before = this._inputBuffer.slice(0, this._cursorPos).join('').trimEnd();
|
|
212
|
+
const lastSpace = before.lastIndexOf(' ');
|
|
213
|
+
const newPos = lastSpace === -1 ? 0 : lastSpace + 1;
|
|
214
|
+
this._inputBuffer = [
|
|
215
|
+
...this._inputBuffer.slice(0, newPos),
|
|
216
|
+
...this._inputBuffer.slice(this._cursorPos),
|
|
217
|
+
];
|
|
218
|
+
this._cursorPos = newPos;
|
|
219
|
+
this._redraw();
|
|
220
|
+
return;
|
|
210
221
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
this.
|
|
222
|
+
// Ctrl+A / Home
|
|
223
|
+
if (key === '\x01' || hex === '1b5b48' || hex === '1b4f48') {
|
|
224
|
+
this._cursorPos = 0;
|
|
225
|
+
this._redraw();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Ctrl+E / End
|
|
229
|
+
if (key === '\x05' || hex === '1b5b46' || hex === '1b4f46') {
|
|
230
|
+
this._cursorPos = this._inputBuffer.length;
|
|
231
|
+
this._redraw();
|
|
232
|
+
return;
|
|
214
233
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
this.
|
|
219
|
-
this.
|
|
234
|
+
// Arrow Left
|
|
235
|
+
if (hex === '1b5b44') {
|
|
236
|
+
if (this._cursorPos > 0) {
|
|
237
|
+
this._cursorPos--;
|
|
238
|
+
this._redraw();
|
|
220
239
|
}
|
|
240
|
+
return;
|
|
221
241
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.
|
|
242
|
+
// Arrow Right
|
|
243
|
+
if (hex === '1b5b43') {
|
|
244
|
+
if (this._cursorPos < this._inputBuffer.length) {
|
|
245
|
+
this._cursorPos++;
|
|
246
|
+
this._redraw();
|
|
226
247
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// Arrow Up — last history
|
|
251
|
+
if (hex === '1b5b41') {
|
|
252
|
+
if (this.history.length > 0) {
|
|
253
|
+
this._inputBuffer = this.history[0].split('');
|
|
254
|
+
this._cursorPos = this._inputBuffer.length;
|
|
255
|
+
this._redraw();
|
|
230
256
|
}
|
|
231
|
-
|
|
257
|
+
return;
|
|
232
258
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.
|
|
259
|
+
// Arrow Down — clear
|
|
260
|
+
if (hex === '1b5b42') {
|
|
261
|
+
if (this._inputBuffer.length > 0) {
|
|
262
|
+
this._inputBuffer = [];
|
|
263
|
+
this._cursorPos = 0;
|
|
264
|
+
this._redraw();
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// Shift/Alt+Enter — insert newline (various terminals)
|
|
269
|
+
if (hex === '1b0d' || hex === '1b0a' ||
|
|
270
|
+
hex === '1b5b31333b327e' || hex === '1b5b31333b3275' ||
|
|
271
|
+
hex === '1b4f4d') {
|
|
272
|
+
this._inputBuffer.splice(this._cursorPos, 0, '\n');
|
|
273
|
+
this._cursorPos++;
|
|
274
|
+
this._redraw();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// Regular typed characters.
|
|
278
|
+
// Shift+Enter fallback: gnome-terminal sends '\' + LF (or sometimes just '\').
|
|
279
|
+
// Collapse a '\' — with an optional trailing LF/CR — into a single newline.
|
|
280
|
+
// Literal '\' still works through paste (Ctrl+Shift+V) since that path is
|
|
281
|
+
// handled by the bracketed-paste branch.
|
|
282
|
+
if (key.length >= 1 && !key.startsWith('\x1b')) {
|
|
283
|
+
const raw = [...key];
|
|
284
|
+
const chars = [];
|
|
285
|
+
for (let i = 0; i < raw.length; i++) {
|
|
286
|
+
const c = raw[i];
|
|
287
|
+
if (c === '\\') {
|
|
288
|
+
chars.push('\n');
|
|
289
|
+
if (raw[i + 1] === '\n' || raw[i + 1] === '\r')
|
|
290
|
+
i++;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (c === '\n' || c.charCodeAt(0) >= 32)
|
|
294
|
+
chars.push(c);
|
|
295
|
+
}
|
|
296
|
+
if (chars.length) {
|
|
297
|
+
this._inputBuffer.splice(this._cursorPos, 0, ...chars);
|
|
298
|
+
this._cursorPos += chars.length;
|
|
299
|
+
this._redraw();
|
|
300
|
+
}
|
|
236
301
|
}
|
|
237
302
|
};
|
|
238
303
|
process.stdin.on('data', onData);
|
|
239
304
|
});
|
|
240
305
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.
|
|
306
|
+
_commitPaste() {
|
|
307
|
+
const text = this._pasteAccum.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
308
|
+
const chars = [...text];
|
|
309
|
+
this._inputBuffer.splice(this._cursorPos, 0, ...chars);
|
|
310
|
+
this._cursorPos += chars.length;
|
|
311
|
+
this._pasting = false;
|
|
312
|
+
this._pasteAccum = '';
|
|
313
|
+
this._redraw();
|
|
246
314
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
process.stdout.write(`\x1b[1;${sb}r`);
|
|
261
|
-
}
|
|
262
|
-
_clearReserved() {
|
|
263
|
-
for (let r = this.scrollBottom + 1; r <= this.rows; r++)
|
|
264
|
-
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
265
|
-
}
|
|
266
|
-
_drawBox() {
|
|
267
|
-
process.stdout.write('\x1b[?25l');
|
|
268
|
-
this._clearReserved();
|
|
269
|
-
if (this._activityHeader !== null) {
|
|
270
|
-
this._drawActivityBox();
|
|
315
|
+
/** Build a string that erases the currently-drawn area and leaves the cursor at col 0 of the top row. */
|
|
316
|
+
_buildClear() {
|
|
317
|
+
if (this._areaRows === 0)
|
|
318
|
+
return '';
|
|
319
|
+
let s = '\r';
|
|
320
|
+
// Move up to first row of area.
|
|
321
|
+
if (this._cursorRow > 0)
|
|
322
|
+
s += `\x1b[${this._cursorRow}A`;
|
|
323
|
+
// Clear each row (line-by-line — most portable).
|
|
324
|
+
for (let i = 0; i < this._areaRows; i++) {
|
|
325
|
+
s += '\x1b[2K';
|
|
326
|
+
if (i < this._areaRows - 1)
|
|
327
|
+
s += '\x1b[1B';
|
|
271
328
|
}
|
|
272
|
-
|
|
273
|
-
|
|
329
|
+
// Back to first row, col 0.
|
|
330
|
+
if (this._areaRows > 1)
|
|
331
|
+
s += `\x1b[${this._areaRows - 1}A`;
|
|
332
|
+
s += '\r';
|
|
333
|
+
return s;
|
|
274
334
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const dashFill = Math.max(0, cols - 3 - header.length);
|
|
282
|
-
// Top border with header text
|
|
283
|
-
process.stdout.write(`\x1b[${topRow};1H`);
|
|
284
|
-
process.stdout.write(T('╭─') + chalk.bold.white(header) + T('─'.repeat(dashFill)) + T('╮'));
|
|
285
|
-
// Content rows (last ACTIVITY_LINES lines, or blank)
|
|
286
|
-
for (let i = 0; i < ACTIVITY_LINES; i++) {
|
|
287
|
-
const row = topRow + 1 + i;
|
|
288
|
-
const line = (this._activityLines[i] ?? '').slice(0, inner);
|
|
289
|
-
const pad = inner - line.length;
|
|
290
|
-
process.stdout.write(`\x1b[${row};1H`);
|
|
291
|
-
process.stdout.write(T('│') + ' ' + chalk.rgb(180, 210, 210)(line) + ' '.repeat(pad) + ' ' + T('│'));
|
|
292
|
-
}
|
|
293
|
-
// Bottom border
|
|
294
|
-
process.stdout.write(`\x1b[${topRow + ACTIVITY_LINES + 1};1H`);
|
|
295
|
-
process.stdout.write(T('╰') + T('─'.repeat(cols - 2)) + T('╯'));
|
|
335
|
+
_clearArea() {
|
|
336
|
+
const s = this._buildClear();
|
|
337
|
+
if (s)
|
|
338
|
+
process.stdout.write(s);
|
|
339
|
+
this._areaRows = 0;
|
|
340
|
+
this._cursorRow = 0;
|
|
296
341
|
}
|
|
297
|
-
|
|
298
|
-
|
|
342
|
+
_redraw() {
|
|
343
|
+
const clear = this._buildClear();
|
|
344
|
+
if (!this._inputActive && this._activityHeader === null) {
|
|
345
|
+
if (clear)
|
|
346
|
+
process.stdout.write(clear);
|
|
347
|
+
this._areaRows = 0;
|
|
348
|
+
this._cursorRow = 0;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
299
351
|
const cols = this.cols;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const pfx = (i === 0) ? PREFIX : PREFIX_CONT;
|
|
319
|
-
process.stdout.write(`\x1b[${row};1H`);
|
|
320
|
-
process.stdout.write(pfx + line);
|
|
321
|
-
process.stdout.write(`\x1b[${cols}G` + T('│'));
|
|
352
|
+
let body = '';
|
|
353
|
+
let row = 0;
|
|
354
|
+
let cursorRow = 0;
|
|
355
|
+
let cursorCol = 0;
|
|
356
|
+
// Activity box
|
|
357
|
+
if (this._activityHeader !== null) {
|
|
358
|
+
const header = (this._activityHeader || '').slice(0, cols - 4);
|
|
359
|
+
const topPad = Math.max(0, cols - 3 - header.length);
|
|
360
|
+
body += DIM('┌') + chalk.bold.white(header) + DIM('─'.repeat(topPad) + '┐') + '\n';
|
|
361
|
+
row++;
|
|
362
|
+
for (let i = 0; i < 5; i++) {
|
|
363
|
+
const line = (this._activityLines[i] ?? '').slice(0, cols - 4);
|
|
364
|
+
const pad = Math.max(0, cols - 4 - line.length);
|
|
365
|
+
body += DIM('│') + ' ' + chalk.rgb(180, 210, 210)(line) + ' '.repeat(pad) + ' ' + DIM('│') + '\n';
|
|
366
|
+
row++;
|
|
367
|
+
}
|
|
368
|
+
body += DIM('└') + DIM('─'.repeat(cols - 2)) + DIM('┘') + '\n';
|
|
369
|
+
row++;
|
|
322
370
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
371
|
+
let trailing = '';
|
|
372
|
+
let lastRow = row;
|
|
373
|
+
if (this._inputActive) {
|
|
374
|
+
const text = this._inputBuffer.join('');
|
|
375
|
+
const beforeCursor = this._inputBuffer.slice(0, this._cursorPos).join('');
|
|
376
|
+
const cursorLineIdx = beforeCursor.split('\n').length - 1;
|
|
377
|
+
const cursorColInLine = (beforeCursor.split('\n').pop() ?? '').length;
|
|
378
|
+
const logicalLines = text.length === 0 ? [''] : text.split('\n');
|
|
379
|
+
const displayLines = logicalLines.map((l, i) => (i === 0 ? PROMPT : INDENT) + (text === '' ? DIM(PLACEHOLDER) : l.slice(0, Math.max(0, cols - 3))));
|
|
380
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
381
|
+
const isLast = i === displayLines.length - 1;
|
|
382
|
+
body += displayLines[i] + (isLast ? '' : '\n');
|
|
383
|
+
if (i === cursorLineIdx) {
|
|
384
|
+
cursorRow = row;
|
|
385
|
+
cursorCol = PROMPT_W + Math.min(cursorColInLine, Math.max(0, cols - 3));
|
|
386
|
+
}
|
|
387
|
+
if (!isLast)
|
|
388
|
+
row++;
|
|
341
389
|
}
|
|
342
|
-
|
|
343
|
-
|
|
390
|
+
lastRow = row;
|
|
391
|
+
// After writing the body, terminal cursor is at end of last drawn line. Reposition to (cursorRow, cursorCol).
|
|
392
|
+
trailing = '\r';
|
|
393
|
+
const upBy = lastRow - cursorRow;
|
|
394
|
+
if (upBy > 0)
|
|
395
|
+
trailing += `\x1b[${upBy}A`;
|
|
396
|
+
if (cursorCol > 0)
|
|
397
|
+
trailing += `\x1b[${cursorCol}C`;
|
|
398
|
+
this._areaRows = lastRow + 1;
|
|
399
|
+
this._cursorRow = cursorRow;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
// Activity-only — leave cursor on the blank line just below the box.
|
|
403
|
+
this._areaRows = row;
|
|
404
|
+
this._cursorRow = row;
|
|
344
405
|
}
|
|
345
|
-
|
|
406
|
+
// Single atomic write: clear + body + reposition.
|
|
407
|
+
process.stdout.write(clear + body + trailing);
|
|
346
408
|
}
|
|
347
409
|
}
|