micro-models-agent 0.41.2 → 0.43.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 (171) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +19 -9
  3. package/dist/cli/completer.js +37 -36
  4. package/dist/cli/index.js +2 -2
  5. package/dist/cli/main.js +23 -48
  6. package/dist/cli/repl-commands.js +12 -40
  7. package/dist/cli/repl.js +87 -217
  8. package/dist/cli/security-commands.js +7 -5
  9. package/dist/cli/setup.js +26 -8
  10. package/dist/config/config.js +5 -52
  11. package/dist/config/defaults.js +5 -29
  12. package/dist/config/experts.js +1 -1
  13. package/dist/config/index.js +3 -3
  14. package/dist/config/security.js +10 -3
  15. package/dist/core/agent-moe.js +10 -2
  16. package/dist/core/agent.js +82 -273
  17. package/dist/core/bootstrap.js +13 -80
  18. package/dist/core/index.js +2 -2
  19. package/dist/core/prompt-builder.js +2 -23
  20. package/dist/core/session-logger.js +4 -46
  21. package/dist/i18n/en.json +2 -75
  22. package/dist/i18n/ru.json +1 -74
  23. package/dist/index.js +1 -1
  24. package/dist/llm/image-utils.js +5 -4
  25. package/dist/llm/index.js +4 -4
  26. package/dist/llm/model-loader.js +6 -6
  27. package/dist/llm/openai-compat.js +34 -40
  28. package/dist/llm/orchestrator.js +29 -33
  29. package/dist/llm/response.js +9 -9
  30. package/dist/logger/app-logger.js +1 -1
  31. package/dist/logger/index.js +1 -1
  32. package/dist/main.js +648 -183
  33. package/dist/migration/backup.js +13 -13
  34. package/dist/migration/detect.js +11 -11
  35. package/dist/migration/index.js +2 -2
  36. package/dist/modules/browser/actions.js +4 -34
  37. package/dist/modules/browser/bridge-server.mjs +202 -202
  38. package/dist/modules/browser/cookie-store.js +6 -6
  39. package/dist/modules/browser/index.js +5 -7
  40. package/dist/modules/browser/module.js +7 -8
  41. package/dist/modules/browser/session.js +84 -87
  42. package/dist/modules/browser/snapshot.js +58 -92
  43. package/dist/modules/browser/types.js +1 -4
  44. package/dist/modules/certification/cli.js +4 -2
  45. package/dist/modules/certification/fact-checker.js +3 -1
  46. package/dist/modules/certification/loader.js +9 -3
  47. package/dist/modules/certification/runner.js +4 -1
  48. package/dist/modules/context/index.js +1 -1
  49. package/dist/modules/context/manager.js +86 -160
  50. package/dist/modules/execution/auditor.js +25 -177
  51. package/dist/modules/execution/module.js +544 -201
  52. package/dist/modules/execution/moe-executor.js +0 -25
  53. package/dist/modules/execution/plan-store.js +3 -1
  54. package/dist/modules/execution/plan-validator.js +10 -10
  55. package/dist/modules/execution/planner.js +1 -6
  56. package/dist/modules/execution/stuck-detector.js +10 -173
  57. package/dist/modules/execution/verifier.js +42 -86
  58. package/dist/modules/hallucination/confidence.js +1 -8
  59. package/dist/modules/hallucination/detector.js +5 -2
  60. package/dist/modules/hallucination/factual.js +64 -3
  61. package/dist/modules/hallucination/index.js +1 -1
  62. package/dist/modules/hallucination/js-identifiers.js +0 -190
  63. package/dist/modules/hallucination/llm-judge.js +3 -1
  64. package/dist/modules/indexer/cache.js +7 -9
  65. package/dist/modules/indexer/index.js +3 -3
  66. package/dist/modules/indexer/module.js +42 -95
  67. package/dist/modules/indexer/walker.js +17 -17
  68. package/dist/modules/lsp/client.js +31 -74
  69. package/dist/modules/lsp/config.js +33 -87
  70. package/dist/modules/lsp/index.js +3 -3
  71. package/dist/modules/lsp/module.js +21 -185
  72. package/dist/modules/mcp/module.js +6 -2
  73. package/dist/modules/memory/index.js +1 -1
  74. package/dist/modules/memory/module.js +23 -71
  75. package/dist/modules/memory/search.js +9 -11
  76. package/dist/modules/memory/store.js +13 -13
  77. package/dist/modules/pipelines/engine.js +10 -10
  78. package/dist/modules/pipelines/index.js +3 -3
  79. package/dist/modules/pipelines/parser.js +14 -17
  80. package/dist/modules/pipelines/template.js +1 -1
  81. package/dist/modules/plugins/builtin/lint-on-write.js +16 -21
  82. package/dist/modules/plugins/builtin/notify.js +2 -3
  83. package/dist/modules/plugins/index.js +1 -1
  84. package/dist/modules/plugins/loader.js +17 -59
  85. package/dist/modules/plugins/manager.js +17 -73
  86. package/dist/modules/processes/index.js +1 -1
  87. package/dist/modules/processes/registry.js +46 -135
  88. package/dist/modules/registry.js +2 -4
  89. package/dist/modules/security/audit-notifier.js +39 -39
  90. package/dist/modules/security/command-validator.js +8 -2
  91. package/dist/modules/security/data-sanitizer.js +9 -1
  92. package/dist/modules/security/encryption.js +56 -58
  93. package/dist/modules/security/network-validator.js +9 -1
  94. package/dist/modules/security/path-validator.js +3 -1
  95. package/dist/modules/security/security-policies.js +19 -3
  96. package/dist/modules/security/session-encryption.js +1 -1
  97. package/dist/modules/security/session-isolation.js +8 -8
  98. package/dist/modules/session/index.js +3 -3
  99. package/dist/modules/session/module.js +5 -5
  100. package/dist/modules/session/store.js +9 -3
  101. package/dist/modules/skills/module.js +2 -1
  102. package/dist/modules/updater/checker.js +6 -70
  103. package/dist/modules/updater/index.js +1 -2
  104. package/dist/modules/user-profile/compressor.js +2 -2
  105. package/dist/modules/user-profile/index.js +1 -1
  106. package/dist/modules/user-profile/profile.js +9 -9
  107. package/dist/tools/attach-image.js +1 -1
  108. package/dist/tools/bash.js +19 -178
  109. package/dist/tools/browser.js +29 -46
  110. package/dist/tools/executor.js +5 -4
  111. package/dist/tools/file-info.js +12 -13
  112. package/dist/tools/filter-tools.js +2 -9
  113. package/dist/tools/glob-tool.js +11 -11
  114. package/dist/tools/grep-tool.js +3 -1
  115. package/dist/tools/index.js +2 -13
  116. package/dist/tools/list-dir.js +17 -18
  117. package/dist/tools/load-skill.js +3 -1
  118. package/dist/tools/path-utils.js +4 -4
  119. package/dist/tools/pipeline-run.js +25 -25
  120. package/dist/tools/process-kill.js +11 -11
  121. package/dist/tools/process-list.js +22 -20
  122. package/dist/tools/process-log.js +18 -22
  123. package/dist/tools/question.js +3 -1
  124. package/dist/tools/read-file.js +2 -10
  125. package/dist/tools/recall.js +37 -44
  126. package/dist/tools/registry.js +4 -15
  127. package/dist/tools/remember.js +29 -29
  128. package/dist/tools/scope-check.js +9 -9
  129. package/dist/tools/subagent.js +9 -54
  130. package/dist/tools/user-input.js +1 -1
  131. package/dist/tools/web-browse.js +3 -3
  132. package/dist/tools/web-fetch.js +3 -3
  133. package/dist/tools/web-search.js +3 -3
  134. package/dist/tools/write-file.js +3 -1
  135. package/dist/ui/box.js +5 -1
  136. package/dist/ui/index.js +6 -6
  137. package/dist/ui/md-formatter.js +33 -33
  138. package/dist/ui/output.js +5 -5
  139. package/dist/ui/renderer.js +10 -15
  140. package/dist/ui/table.js +1 -1
  141. package/package.json +48 -48
  142. package/dist/cli/plugin-commands.js +0 -36
  143. package/dist/cli/run-result.js +0 -22
  144. package/dist/core/version.js +0 -24
  145. package/dist/modules/artifacts/store.js +0 -61
  146. package/dist/modules/browser/bridge-client.js +0 -199
  147. package/dist/modules/browser/bridge-path.js +0 -10
  148. package/dist/modules/browser/driver.js +0 -136
  149. package/dist/modules/context/chunk-query.js +0 -100
  150. package/dist/modules/context/fact-extractor.js +0 -162
  151. package/dist/modules/context/history.js +0 -15
  152. package/dist/modules/execution/audit-runners.js +0 -152
  153. package/dist/modules/execution/execution-plugin.js +0 -272
  154. package/dist/modules/execution/plan-tool.js +0 -508
  155. package/dist/modules/execution/windows-commands.js +0 -41
  156. package/dist/modules/indexer/project-profile.js +0 -183
  157. package/dist/modules/lsp/check-tool.js +0 -58
  158. package/dist/modules/lsp/command.js +0 -60
  159. package/dist/modules/lsp/probe.js +0 -76
  160. package/dist/modules/lsp/project-root.js +0 -32
  161. package/dist/modules/lsp/startup-check.js +0 -141
  162. package/dist/modules/processes/detect.js +0 -34
  163. package/dist/modules/skills/matcher.js +0 -27
  164. package/dist/modules/updater/module.js +0 -116
  165. package/dist/tools/chunk-query.js +0 -99
  166. package/dist/tools/download-file.js +0 -116
  167. package/dist/tools/enable-tools.js +0 -58
  168. package/dist/tools/hidden-tools-block.js +0 -37
  169. package/dist/ui/line-editor.js +0 -703
  170. package/dist/ui/line-math.js +0 -69
  171. package/dist/ui/plan-view.js +0 -103
@@ -1,703 +0,0 @@
1
- import * as readline from "readline";
2
- import stringWidth from "string-width";
3
- import { buildLayout, layoutIndexFor, visualColAt, charOffsetFor, charLen, stripAnsi, } from "./line-math";
4
- export class LineEditor {
5
- input;
6
- output;
7
- /** Host hook: return true to swallow the key before the editor handles it. */
8
- onKeyInput;
9
- promptStr;
10
- completer;
11
- history;
12
- historySize;
13
- lines = [""];
14
- row = 0;
15
- col = 0;
16
- historyIndex = -1;
17
- stash = null;
18
- isDone = false;
19
- isPaste = false;
20
- /** Pasted characters accumulated between `paste-start` and `paste-end`.
21
- * Inserted as a single batch on `paste-end` — one render instead of one
22
- * per keypress, so a paste appears as a paste, not as fast typing. */
23
- pasteBuffer = "";
24
- /** Whether the last paste keypress was a bare CR — used to collapse a
25
- * CRLF pair into a single newline (Windows clipboard text). */
26
- pasteLastWasCR = false;
27
- lastWasCR = false;
28
- questionCb = null;
29
- rawMode = false;
30
- listeners = {};
31
- /** Visual row (within the drawn frame) where the cursor was placed by the
32
- * last render — used to move back to the frame's first row before
33
- * redrawing, so a redraw starting on a middle/bottom row never leaves
34
- * stale lines above (duplicated input text on newline). */
35
- prevCursorRow = 0;
36
- constructor(opts) {
37
- this.input = opts.input;
38
- this.output = opts.output;
39
- this.promptStr = opts.prompt ?? "";
40
- this.completer = opts.completer;
41
- this.history = opts.history ?? [];
42
- this.historySize = opts.historySize ?? 50;
43
- if (this.history.length > this.historySize) {
44
- this.history = this.history.slice(-this.historySize);
45
- }
46
- if (this.input.isTTY) {
47
- this.rawMode = typeof this.input.setRawMode === "function";
48
- if (this.rawMode)
49
- this.input.setRawMode(true);
50
- this.enableTerminalProtocols();
51
- readline.emitKeypressEvents(this.input);
52
- this.input.on("keypress", (str, key) => this.onKey(str, key));
53
- }
54
- else {
55
- this.input.on("data", (buf) => this.onData(buf));
56
- this.input.on("end", () => {
57
- const last = this.lines.join("\n");
58
- this.lines = [""];
59
- this.row = 0;
60
- this.col = 0;
61
- if (last)
62
- this.emitLine(last);
63
- this.emitClose();
64
- });
65
- }
66
- }
67
- on(event, listener) {
68
- if (event === "line") {
69
- this.listeners.line = listener;
70
- }
71
- else {
72
- this.listeners.close = listener;
73
- }
74
- return this;
75
- }
76
- setPrompt(prompt) {
77
- this.promptStr = prompt;
78
- if (this.input.isTTY)
79
- this.render();
80
- }
81
- prompt(_preserveCursor) {
82
- if (this.input.isTTY)
83
- this.render();
84
- }
85
- write(s) {
86
- this.insertText(s);
87
- }
88
- reset() {
89
- this.lines = [""];
90
- this.row = 0;
91
- this.col = 0;
92
- this.historyIndex = -1;
93
- this.stash = null;
94
- this.prevCursorRow = 0;
95
- if (this.input.isTTY)
96
- this.render();
97
- }
98
- question(query, cb) {
99
- this.questionCb = cb;
100
- this.promptStr = query;
101
- this.lines = [""];
102
- this.row = 0;
103
- this.col = 0;
104
- this.prevCursorRow = 0;
105
- if (this.input.isTTY)
106
- this.render();
107
- }
108
- close() {
109
- if (this.isDone)
110
- return;
111
- this.isDone = true;
112
- this.disableTerminalProtocols();
113
- if (this.rawMode && this.input.setRawMode) {
114
- this.input.setRawMode(false);
115
- }
116
- this.emitClose();
117
- }
118
- // ---- input handling ----
119
- onData(buf) {
120
- if (this.isDone)
121
- return;
122
- let text = buf.toString("utf-8");
123
- text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
124
- const parts = text.split("\n");
125
- for (let i = 0; i < parts.length; i++) {
126
- if (i < parts.length - 1) {
127
- this.emitLine(parts[i]);
128
- this.lines = [""];
129
- this.row = 0;
130
- this.col = 0;
131
- }
132
- else if (parts[i]) {
133
- this.emitLine(parts[i]);
134
- this.lines = [""];
135
- this.row = 0;
136
- this.col = 0;
137
- }
138
- }
139
- }
140
- onKey(str, key) {
141
- if (this.isDone)
142
- return;
143
- if (this.onKeyInput && this.onKeyInput(str, key))
144
- return;
145
- const k = key;
146
- const name = k?.name ?? "undefined";
147
- const seq = k?.sequence ?? str ?? "";
148
- if (name === "undefined" && /^\x1b\[\d+(?:;\d+)?u$/.test(seq)) {
149
- const m = seq.match(/^\x1b\[(\d+)(?:;(\d+))?u$/);
150
- if (m && Number(m[1]) === 13 && Number(m[2] ?? 0) !== 0) {
151
- this.insertNewline();
152
- }
153
- return;
154
- }
155
- if (name === "paste-start" || seq === "\x1b[200~") {
156
- this.isPaste = true;
157
- this.pasteBuffer = "";
158
- this.pasteLastWasCR = false;
159
- return;
160
- }
161
- if (name === "paste-end" || seq === "\x1b[201~") {
162
- this.isPaste = false;
163
- if (this.pasteBuffer) {
164
- this.insertPaste(this.pasteBuffer);
165
- }
166
- else {
167
- this.render();
168
- }
169
- this.pasteBuffer = "";
170
- this.pasteLastWasCR = false;
171
- return;
172
- }
173
- if (this.isPaste) {
174
- if (name === "return" || seq === "\r") {
175
- this.pasteBuffer += "\n";
176
- this.pasteLastWasCR = true;
177
- }
178
- else if (name === "enter" || seq === "\n") {
179
- if (this.pasteLastWasCR) {
180
- this.pasteLastWasCR = false;
181
- }
182
- else {
183
- this.pasteBuffer += "\n";
184
- }
185
- }
186
- else if (str) {
187
- this.pasteLastWasCR = false;
188
- this.pasteBuffer += str;
189
- }
190
- return;
191
- }
192
- if (name === "return" && (seq === "\x1b\r" || k.meta)) {
193
- this.insertNewline();
194
- return;
195
- }
196
- if (name === "undefined" && seq === "\x1b\r") {
197
- this.insertNewline();
198
- return;
199
- }
200
- if (k?.ctrl && name === "j") {
201
- this.insertNewline();
202
- return;
203
- }
204
- if (name === "escape")
205
- return;
206
- switch (name) {
207
- case "return":
208
- this.lastWasCR = true;
209
- this.submit();
210
- return;
211
- case "enter":
212
- if (this.lastWasCR) {
213
- this.lastWasCR = false;
214
- return;
215
- }
216
- this.insertNewline();
217
- return;
218
- case "backspace":
219
- this.lastWasCR = false;
220
- this.backspace();
221
- return;
222
- case "delete":
223
- this.lastWasCR = false;
224
- this.deleteForward();
225
- return;
226
- case "left":
227
- this.lastWasCR = false;
228
- this.moveLeft();
229
- return;
230
- case "right":
231
- this.lastWasCR = false;
232
- this.moveRight();
233
- return;
234
- case "up":
235
- this.lastWasCR = false;
236
- this.moveUp();
237
- return;
238
- case "down":
239
- this.lastWasCR = false;
240
- this.moveDown();
241
- return;
242
- case "home":
243
- this.lastWasCR = false;
244
- this.moveHome();
245
- return;
246
- case "end":
247
- this.lastWasCR = false;
248
- this.moveEnd();
249
- return;
250
- case "pageup":
251
- this.lastWasCR = false;
252
- this.moveToStart();
253
- return;
254
- case "pagedown":
255
- this.lastWasCR = false;
256
- this.moveToEnd();
257
- return;
258
- case "tab":
259
- this.lastWasCR = false;
260
- this.tabComplete();
261
- return;
262
- }
263
- if (k?.ctrl) {
264
- this.lastWasCR = false;
265
- switch (name) {
266
- case "c":
267
- this.ctrlC();
268
- return;
269
- case "d":
270
- this.ctrlD();
271
- return;
272
- case "a":
273
- this.moveHome();
274
- return;
275
- case "e":
276
- this.moveEnd();
277
- return;
278
- case "u":
279
- this.killToStart();
280
- return;
281
- case "k":
282
- this.killToEnd();
283
- return;
284
- case "w":
285
- this.killWordBefore();
286
- return;
287
- case "l":
288
- this.clearScreen();
289
- return;
290
- default:
291
- return;
292
- }
293
- }
294
- if (k?.meta) {
295
- this.lastWasCR = false;
296
- switch (name) {
297
- case "b":
298
- this.moveWordLeft();
299
- return;
300
- case "f":
301
- this.moveWordRight();
302
- return;
303
- case "d":
304
- this.killWordAfter();
305
- return;
306
- default:
307
- return;
308
- }
309
- }
310
- if (str && !/^[\u0000-\u001f\u007f]/.test(str)) {
311
- this.lastWasCR = false;
312
- this.insertText(str);
313
- }
314
- }
315
- submit() {
316
- if (this.questionCb) {
317
- const cb = this.questionCb;
318
- this.questionCb = null;
319
- const answer = this.lines.join("\n").trim();
320
- this.commitFrame();
321
- this.output.write("\r\n");
322
- this.lines = [""];
323
- this.row = 0;
324
- this.col = 0;
325
- this.prevCursorRow = 0;
326
- cb(answer);
327
- return;
328
- }
329
- const text = this.lines.join("\n");
330
- if (text.trim()) {
331
- this.history.push(text);
332
- if (this.history.length > this.historySize) {
333
- this.history = this.history.slice(-this.historySize);
334
- }
335
- }
336
- this.historyIndex = -1;
337
- this.stash = null;
338
- this.commitFrame();
339
- this.output.write("\r\n");
340
- this.lines = [""];
341
- this.row = 0;
342
- this.col = 0;
343
- this.prevCursorRow = 0;
344
- this.emitLine(text);
345
- }
346
- /** Move the cursor to the end of the buffer's last visual row so the
347
- * submitted text stays on screen (commit), without erasing the frame. */
348
- commitFrame() {
349
- const layout = this.layout();
350
- const idx = layoutIndexFor(layout, this.row, this.col);
351
- const rowsDown = layout.length - 1 - idx;
352
- const lastLen = charLen(stripAnsi(layout[layout.length - 1].text));
353
- const out = [];
354
- if (rowsDown > 0)
355
- out.push(`\x1b[${rowsDown}B`);
356
- out.push("\r");
357
- if (lastLen > 0)
358
- out.push(`\x1b[${lastLen}C`);
359
- this.output.write(out.join(""));
360
- }
361
- ctrlC() {
362
- this.lines = [""];
363
- this.row = 0;
364
- this.col = 0;
365
- this.prevCursorRow = 0;
366
- this.render();
367
- process.emit("SIGINT");
368
- }
369
- ctrlD() {
370
- if (this.lines.length === 1 && this.lines[0] === "") {
371
- this.emitClose();
372
- }
373
- else {
374
- this.deleteForward();
375
- }
376
- }
377
- // ---- editing primitives ----
378
- currentChars() {
379
- return Array.from(this.lines[this.row]);
380
- }
381
- setCurrentChars(chars) {
382
- this.lines[this.row] = chars.join("");
383
- }
384
- insertText(text) {
385
- const sanitized = text.replace(/\r\n/g, "\n").replace(/\r/g, "");
386
- const chars = this.currentChars();
387
- const t = Array.from(sanitized);
388
- chars.splice(this.col, 0, ...t);
389
- this.setCurrentChars(chars);
390
- this.col += t.length;
391
- this.render();
392
- }
393
- /** Bulk-insert pasted text at the cursor. Splits on newlines so the buffer
394
- * keeps one string per line (instead of embedding literal `\n` chars),
395
- * and renders once — a paste must look like a paste, not like typing. */
396
- insertPaste(text) {
397
- const segments = text.split("\n");
398
- if (segments.length === 1) {
399
- this.insertText(segments[0]);
400
- return;
401
- }
402
- const chars = this.currentChars();
403
- const head = chars.slice(0, this.col).join("");
404
- const tail = chars.slice(this.col).join("");
405
- const lines = [head + segments[0]];
406
- for (let i = 1; i < segments.length - 1; i++)
407
- lines.push(segments[i]);
408
- lines.push(segments[segments.length - 1] + tail);
409
- this.lines.splice(this.row, 1, ...lines);
410
- this.row += segments.length - 1;
411
- this.col = charLen(segments[segments.length - 1]);
412
- this.render();
413
- }
414
- insertNewline() {
415
- const chars = this.currentChars();
416
- const rest = chars.slice(this.col).join("");
417
- this.setCurrentChars(chars.slice(0, this.col));
418
- this.lines.splice(this.row + 1, 0, rest);
419
- this.row++;
420
- this.col = 0;
421
- this.render();
422
- }
423
- backspace() {
424
- const chars = this.currentChars();
425
- if (this.col > 0) {
426
- chars.splice(this.col - 1, 1);
427
- this.setCurrentChars(chars);
428
- this.col--;
429
- }
430
- else if (this.row > 0) {
431
- const prevLen = charLen(this.lines[this.row - 1]);
432
- this.lines[this.row - 1] = this.lines[this.row - 1] + this.lines[this.row];
433
- this.lines.splice(this.row, 1);
434
- this.row--;
435
- this.col = prevLen;
436
- }
437
- this.render();
438
- }
439
- deleteForward() {
440
- const chars = this.currentChars();
441
- if (this.col < chars.length) {
442
- chars.splice(this.col, 1);
443
- this.setCurrentChars(chars);
444
- }
445
- else if (this.row < this.lines.length - 1) {
446
- this.lines[this.row] = this.lines[this.row] + this.lines[this.row + 1];
447
- this.lines.splice(this.row + 1, 1);
448
- }
449
- this.render();
450
- }
451
- killToStart() {
452
- const chars = this.currentChars();
453
- this.setCurrentChars(chars.slice(this.col));
454
- this.col = 0;
455
- this.render();
456
- }
457
- killToEnd() {
458
- const chars = this.currentChars();
459
- this.setCurrentChars(chars.slice(0, this.col));
460
- this.render();
461
- }
462
- killWordBefore() {
463
- const chars = this.currentChars();
464
- let i = this.col;
465
- while (i > 0 && /\s/.test(chars[i - 1]))
466
- i--;
467
- while (i > 0 && !/\s/.test(chars[i - 1]))
468
- i--;
469
- chars.splice(i, this.col - i);
470
- this.setCurrentChars(chars);
471
- this.col = i;
472
- this.render();
473
- }
474
- killWordAfter() {
475
- const chars = this.currentChars();
476
- const n = chars.length;
477
- let i = this.col;
478
- while (i < n && /\s/.test(chars[i]))
479
- i++;
480
- while (i < n && !/\s/.test(chars[i]))
481
- i++;
482
- chars.splice(this.col, i - this.col);
483
- this.setCurrentChars(chars);
484
- this.render();
485
- }
486
- // ---- navigation ----
487
- moveLeft() {
488
- if (this.col > 0) {
489
- this.col--;
490
- }
491
- else if (this.row > 0) {
492
- this.row--;
493
- this.col = charLen(this.lines[this.row]);
494
- }
495
- this.render();
496
- }
497
- moveRight() {
498
- if (this.col < charLen(this.lines[this.row])) {
499
- this.col++;
500
- }
501
- else if (this.row < this.lines.length - 1) {
502
- this.row++;
503
- this.col = 0;
504
- }
505
- this.render();
506
- }
507
- moveUp() {
508
- const layout = this.layout();
509
- const idx = layoutIndexFor(layout, this.row, this.col);
510
- const vcol = visualColAt(layout, idx, this.col);
511
- if (idx > 0) {
512
- const target = layout[idx - 1];
513
- this.row = target.bufRow;
514
- this.col = charOffsetFor(layout, idx - 1, vcol);
515
- this.render();
516
- }
517
- else {
518
- this.historyPrev();
519
- }
520
- }
521
- moveDown() {
522
- const layout = this.layout();
523
- const idx = layoutIndexFor(layout, this.row, this.col);
524
- const vcol = visualColAt(layout, idx, this.col);
525
- if (idx < layout.length - 1) {
526
- const target = layout[idx + 1];
527
- this.row = target.bufRow;
528
- this.col = charOffsetFor(layout, idx + 1, vcol);
529
- this.render();
530
- }
531
- else {
532
- this.historyNext();
533
- }
534
- }
535
- moveHome() {
536
- this.col = 0;
537
- this.render();
538
- }
539
- moveEnd() {
540
- this.col = charLen(this.lines[this.row]);
541
- this.render();
542
- }
543
- moveToStart() {
544
- this.row = 0;
545
- this.col = 0;
546
- this.render();
547
- }
548
- moveToEnd() {
549
- this.row = this.lines.length - 1;
550
- this.col = charLen(this.lines[this.row]);
551
- this.render();
552
- }
553
- moveWordLeft() {
554
- const chars = this.currentChars();
555
- let i = this.col;
556
- while (i > 0 && /\s/.test(chars[i - 1]))
557
- i--;
558
- while (i > 0 && !/\s/.test(chars[i - 1]))
559
- i--;
560
- this.col = i;
561
- this.render();
562
- }
563
- moveWordRight() {
564
- const chars = this.currentChars();
565
- const n = chars.length;
566
- let i = this.col;
567
- while (i < n && !/\s/.test(chars[i]))
568
- i++;
569
- while (i < n && /\s/.test(chars[i]))
570
- i++;
571
- this.col = i;
572
- this.render();
573
- }
574
- historyPrev() {
575
- if (this.history.length === 0)
576
- return;
577
- if (this.historyIndex === -1) {
578
- this.stash = {
579
- lines: this.lines.slice(),
580
- row: this.row,
581
- col: this.col,
582
- };
583
- this.historyIndex = 0;
584
- }
585
- else if (this.historyIndex < this.history.length - 1) {
586
- this.historyIndex++;
587
- }
588
- else {
589
- return;
590
- }
591
- this.applyHistoryEntry();
592
- }
593
- historyNext() {
594
- if (this.historyIndex === -1)
595
- return;
596
- if (this.historyIndex > 0) {
597
- this.historyIndex--;
598
- this.applyHistoryEntry();
599
- }
600
- else if (this.stash) {
601
- this.lines = this.stash.lines.slice();
602
- this.row = this.stash.row;
603
- this.col = this.stash.col;
604
- this.historyIndex = -1;
605
- this.stash = null;
606
- this.render();
607
- }
608
- }
609
- applyHistoryEntry() {
610
- const entry = this.history[this.history.length - 1 - this.historyIndex];
611
- this.lines = entry.split("\n");
612
- if (this.lines.length === 0)
613
- this.lines = [""];
614
- this.row = this.lines.length - 1;
615
- this.col = charLen(this.lines[this.row]);
616
- this.render();
617
- }
618
- // ---- completion / misc ----
619
- tabComplete() {
620
- if (!this.completer)
621
- return;
622
- const line = this.lines[this.row];
623
- const [matches, partial] = this.completer(line);
624
- if (matches.length === 0) {
625
- this.output.write("\x07");
626
- return;
627
- }
628
- if (matches.length === 1) {
629
- const from = line.lastIndexOf(partial);
630
- const head = line.slice(0, from);
631
- const tail = line.slice(from + partial.length);
632
- this.lines[this.row] = head + matches[0] + tail;
633
- this.col = charLen(head) + charLen(matches[0]);
634
- this.render();
635
- return;
636
- }
637
- const lines = matches.map((m) => " " + m);
638
- this.output.write("\r\n" + lines.join("\r\n") + "\r\n");
639
- this.output.write(`\x1b[${lines.length + 1}A`);
640
- this.render();
641
- }
642
- clearScreen() {
643
- this.output.write("\x1b[2J\x1b[H");
644
- this.prevCursorRow = 0;
645
- this.render();
646
- }
647
- // ---- rendering ----
648
- columns() {
649
- return this.output.columns || 80;
650
- }
651
- layout() {
652
- return buildLayout(this.lines, this.columns());
653
- }
654
- render() {
655
- if (!this.input.isTTY || this.isDone)
656
- return;
657
- const layout = this.layout();
658
- const promptWidth = stringWidth(stripAnsi(this.promptStr));
659
- const idx = layoutIndexFor(layout, this.row, this.col);
660
- const vcol = visualColAt(layout, idx, this.col);
661
- const rowsAfter = layout.length - idx - 1;
662
- const ccol = (idx === 0 ? promptWidth : 0) + vcol;
663
- const out = [];
664
- // Move back up to the frame's first row before redrawing. Without this,
665
- // a redraw that starts on a middle/bottom row (e.g. typing on line 2 of
666
- // a multi-line buffer) writes the whole frame one row too low and leaves
667
- // the previous frame's top line stale on screen — the user sees every
668
- // line duplicated.
669
- if (this.prevCursorRow > 0)
670
- out.push(`\x1b[${this.prevCursorRow}A`);
671
- out.push("\r");
672
- for (let i = 0; i < layout.length; i++) {
673
- out.push("\x1b[2K");
674
- if (i === 0)
675
- out.push(this.promptStr);
676
- out.push(layout[i].text);
677
- if (i < layout.length - 1)
678
- out.push("\r\n");
679
- }
680
- out.push("\x1b[J");
681
- if (rowsAfter > 0)
682
- out.push(`\x1b[${rowsAfter}A`);
683
- out.push("\r");
684
- if (ccol > 0)
685
- out.push(`\x1b[${ccol}C`);
686
- this.prevCursorRow = idx;
687
- this.output.write(out.join(""));
688
- }
689
- enableTerminalProtocols() {
690
- this.output.write("\x1b[?2004h");
691
- this.output.write("\x1b[>1u");
692
- }
693
- disableTerminalProtocols() {
694
- this.output.write("\x1b[?2004l");
695
- this.output.write("\x1b[<u");
696
- }
697
- emitLine(line) {
698
- this.listeners.line?.(line);
699
- }
700
- emitClose() {
701
- this.listeners.close?.();
702
- }
703
- }