mrmd-monitor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +517 -0
- package/bin/cli.js +178 -0
- package/package.json +32 -0
- package/src/coordination.js +358 -0
- package/src/document.js +187 -0
- package/src/execution.js +261 -0
- package/src/index.js +14 -0
- package/src/monitor.js +420 -0
- package/src/terminal.js +294 -0
package/src/terminal.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal Buffer for mrmd-monitor
|
|
3
|
+
*
|
|
4
|
+
* Processes streaming terminal output with proper cursor movement and ANSI handling.
|
|
5
|
+
* Enables progress bars (tqdm, rich) to display correctly during execution.
|
|
6
|
+
*
|
|
7
|
+
* This is a simplified version of mrmd-editor's terminal.js for Node.js usage.
|
|
8
|
+
* Output is processed to plain text for document storage.
|
|
9
|
+
*
|
|
10
|
+
* @module mrmd-monitor/terminal
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Terminal buffer that processes cursor movement and ANSI codes
|
|
15
|
+
*/
|
|
16
|
+
export class TerminalBuffer {
|
|
17
|
+
constructor() {
|
|
18
|
+
/** @type {string[][]} Lines of characters */
|
|
19
|
+
this._lines = [[]];
|
|
20
|
+
/** @type {number} Current row */
|
|
21
|
+
this._row = 0;
|
|
22
|
+
/** @type {number} Current column */
|
|
23
|
+
this._col = 0;
|
|
24
|
+
/** @type {{row: number, col: number}|null} Saved cursor position */
|
|
25
|
+
this._savedCursor = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Process terminal output and write to buffer
|
|
30
|
+
* @param {string} text - Raw terminal output with escape sequences
|
|
31
|
+
*/
|
|
32
|
+
write(text) {
|
|
33
|
+
let i = 0;
|
|
34
|
+
|
|
35
|
+
while (i < text.length) {
|
|
36
|
+
// Check for escape sequence
|
|
37
|
+
if (text[i] === '\x1b' && text[i + 1] === '[') {
|
|
38
|
+
i = this._parseEscapeSequence(text, i);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handle special characters
|
|
43
|
+
const char = text[i];
|
|
44
|
+
|
|
45
|
+
if (char === '\r') {
|
|
46
|
+
// Carriage return - back to start of line
|
|
47
|
+
this._col = 0;
|
|
48
|
+
} else if (char === '\n') {
|
|
49
|
+
// Newline - next line, column 0
|
|
50
|
+
this._row++;
|
|
51
|
+
this._col = 0;
|
|
52
|
+
this._ensureRow(this._row);
|
|
53
|
+
} else if (char === '\b') {
|
|
54
|
+
// Backspace
|
|
55
|
+
this._col = Math.max(0, this._col - 1);
|
|
56
|
+
} else if (char === '\t') {
|
|
57
|
+
// Tab - move to next 8-column boundary
|
|
58
|
+
this._col = Math.floor(this._col / 8) * 8 + 8;
|
|
59
|
+
} else if (char.charCodeAt(0) >= 32) {
|
|
60
|
+
// Printable character
|
|
61
|
+
this._writeChar(char);
|
|
62
|
+
}
|
|
63
|
+
// Ignore other control characters
|
|
64
|
+
|
|
65
|
+
i++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parse an escape sequence starting at position i
|
|
71
|
+
* @param {string} text
|
|
72
|
+
* @param {number} i
|
|
73
|
+
* @returns {number} Next index
|
|
74
|
+
*/
|
|
75
|
+
_parseEscapeSequence(text, i) {
|
|
76
|
+
// Skip \x1b[
|
|
77
|
+
let j = i + 2;
|
|
78
|
+
|
|
79
|
+
// Check for DEC private mode prefix '?'
|
|
80
|
+
const isPrivateMode = text[j] === '?';
|
|
81
|
+
if (isPrivateMode) j++;
|
|
82
|
+
|
|
83
|
+
// Collect parameter bytes (digits and semicolons)
|
|
84
|
+
let params = '';
|
|
85
|
+
while (j < text.length && /[0-9;]/.test(text[j])) {
|
|
86
|
+
params += text[j];
|
|
87
|
+
j++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Get command byte
|
|
91
|
+
const cmd = text[j] || '';
|
|
92
|
+
j++;
|
|
93
|
+
|
|
94
|
+
// Ignore DEC private modes and SGR (colors/styles) - we output plain text
|
|
95
|
+
if (isPrivateMode || cmd === 'm') {
|
|
96
|
+
return j;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Parse parameter numbers
|
|
100
|
+
const nums = params ? params.split(';').map(n => parseInt(n) || 0) : [];
|
|
101
|
+
const n = nums[0] || 1;
|
|
102
|
+
|
|
103
|
+
switch (cmd) {
|
|
104
|
+
case 'A': // Cursor Up
|
|
105
|
+
this._row = Math.max(0, this._row - n);
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
case 'B': // Cursor Down
|
|
109
|
+
this._row += n;
|
|
110
|
+
this._ensureRow(this._row);
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case 'C': // Cursor Forward (Right)
|
|
114
|
+
this._col += n;
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case 'D': // Cursor Back (Left)
|
|
118
|
+
this._col = Math.max(0, this._col - n);
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case 'E': // Cursor Next Line
|
|
122
|
+
this._row += n;
|
|
123
|
+
this._col = 0;
|
|
124
|
+
this._ensureRow(this._row);
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case 'F': // Cursor Previous Line
|
|
128
|
+
this._row = Math.max(0, this._row - n);
|
|
129
|
+
this._col = 0;
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 'G': // Cursor Horizontal Absolute
|
|
133
|
+
this._col = Math.max(0, n - 1);
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 'H': // Cursor Position (row;col)
|
|
137
|
+
case 'f':
|
|
138
|
+
this._row = Math.max(0, (nums[0] || 1) - 1);
|
|
139
|
+
this._col = Math.max(0, (nums[1] || 1) - 1);
|
|
140
|
+
this._ensureRow(this._row);
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case 'J': // Erase in Display
|
|
144
|
+
if (n === 0 || params === '') {
|
|
145
|
+
this._clearToEndOfScreen();
|
|
146
|
+
} else if (n === 1) {
|
|
147
|
+
this._clearFromStartOfScreen();
|
|
148
|
+
} else if (n === 2 || n === 3) {
|
|
149
|
+
this._clearScreen();
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
|
|
153
|
+
case 'K': // Erase in Line
|
|
154
|
+
if (n === 0 || params === '') {
|
|
155
|
+
this._clearToEndOfLine();
|
|
156
|
+
} else if (n === 1) {
|
|
157
|
+
this._clearFromStartOfLine();
|
|
158
|
+
} else if (n === 2) {
|
|
159
|
+
this._clearLine();
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case 's': // Save Cursor Position
|
|
164
|
+
this._savedCursor = { row: this._row, col: this._col };
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case 'u': // Restore Cursor Position
|
|
168
|
+
if (this._savedCursor) {
|
|
169
|
+
this._row = this._savedCursor.row;
|
|
170
|
+
this._col = this._savedCursor.col;
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return j;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Write a character at current cursor position
|
|
180
|
+
* @param {string} char
|
|
181
|
+
*/
|
|
182
|
+
_writeChar(char) {
|
|
183
|
+
this._ensureRow(this._row);
|
|
184
|
+
const line = this._lines[this._row];
|
|
185
|
+
|
|
186
|
+
// Extend line if needed
|
|
187
|
+
while (line.length <= this._col) {
|
|
188
|
+
line.push(' ');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Write character
|
|
192
|
+
line[this._col] = char;
|
|
193
|
+
this._col++;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** @param {number} row */
|
|
197
|
+
_ensureRow(row) {
|
|
198
|
+
while (this._lines.length <= row) {
|
|
199
|
+
this._lines.push([]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_clearToEndOfLine() {
|
|
204
|
+
if (this._lines[this._row]) {
|
|
205
|
+
this._lines[this._row] = this._lines[this._row].slice(0, this._col);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_clearFromStartOfLine() {
|
|
210
|
+
if (this._lines[this._row]) {
|
|
211
|
+
const line = this._lines[this._row];
|
|
212
|
+
for (let i = 0; i <= this._col && i < line.length; i++) {
|
|
213
|
+
line[i] = ' ';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_clearLine() {
|
|
219
|
+
this._lines[this._row] = [];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_clearToEndOfScreen() {
|
|
223
|
+
this._clearToEndOfLine();
|
|
224
|
+
for (let r = this._row + 1; r < this._lines.length; r++) {
|
|
225
|
+
this._lines[r] = [];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
_clearFromStartOfScreen() {
|
|
230
|
+
for (let r = 0; r < this._row; r++) {
|
|
231
|
+
this._lines[r] = [];
|
|
232
|
+
}
|
|
233
|
+
this._clearFromStartOfLine();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_clearScreen() {
|
|
237
|
+
this._lines = [[]];
|
|
238
|
+
this._row = 0;
|
|
239
|
+
this._col = 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Convert buffer to plain text (for document storage)
|
|
244
|
+
* @returns {string}
|
|
245
|
+
*/
|
|
246
|
+
toString() {
|
|
247
|
+
const output = [];
|
|
248
|
+
|
|
249
|
+
for (const line of this._lines) {
|
|
250
|
+
let lineText = line.join('');
|
|
251
|
+
// Trim trailing spaces
|
|
252
|
+
output.push(lineText.trimEnd());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Trim trailing empty lines
|
|
256
|
+
while (output.length > 0 && output[output.length - 1] === '') {
|
|
257
|
+
output.pop();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return output.join('\n');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Clear the buffer and reset cursor
|
|
265
|
+
*/
|
|
266
|
+
clear() {
|
|
267
|
+
this._lines = [[]];
|
|
268
|
+
this._row = 0;
|
|
269
|
+
this._col = 0;
|
|
270
|
+
this._savedCursor = null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get current line count
|
|
275
|
+
* @returns {number}
|
|
276
|
+
*/
|
|
277
|
+
get lineCount() {
|
|
278
|
+
return this._lines.length;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Process terminal output through a buffer
|
|
284
|
+
*
|
|
285
|
+
* Convenience function for one-shot processing.
|
|
286
|
+
*
|
|
287
|
+
* @param {string} text - Raw terminal output
|
|
288
|
+
* @returns {string} - Processed plain text
|
|
289
|
+
*/
|
|
290
|
+
export function processTerminalOutput(text) {
|
|
291
|
+
const buffer = new TerminalBuffer();
|
|
292
|
+
buffer.write(text);
|
|
293
|
+
return buffer.toString();
|
|
294
|
+
}
|