smolcoder 0.4.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.
@@ -0,0 +1,632 @@
1
+ "use strict";
2
+ // The inline TUI, styled after opencode: an accent-bar input block with the
3
+ // session status (mode · model · effort · ctx) inside it, a slash-command menu
4
+ // that opens above the input as you type "/", arrow-key pickers with
5
+ // type-to-filter, and shift+tab mode cycling. Hand-rolled ANSI, zero deps.
6
+ //
7
+ // The frame (input block + menus) exists only while waiting for input; while
8
+ // the agent runs, output streams plainly and scrolls naturally.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.Tui = void 0;
11
+ const ui_1 = require("../ui");
12
+ const util_1 = require("../util");
13
+ const editor_1 = require("./editor");
14
+ const keys_1 = require("./keys");
15
+ const ACCENT = "\x1b[36m"; // cyan accent bar
16
+ const RESET = "\x1b[0m";
17
+ const SEL = "\x1b[48;5;31m\x1b[38;5;231m"; // cyan selection bar, white text — one blue theme
18
+ const BAR = `${ACCENT}▌${RESET} `;
19
+ const BOX_BG = "\x1b[48;5;235m"; // subtle shading for the input block
20
+ function visLen(s) {
21
+ // eslint-disable-next-line no-control-regex
22
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
23
+ }
24
+ /** Cut a colored string to a visible length without splitting escape codes. */
25
+ function truncateVisible(s, max) {
26
+ let out = "";
27
+ let seen = 0;
28
+ for (let i = 0; i < s.length; i++) {
29
+ if (s[i] === "\x1b") {
30
+ const m = /^\x1b\[[0-9;]*m/.exec(s.slice(i));
31
+ if (m) {
32
+ out += m[0];
33
+ i += m[0].length - 1;
34
+ continue;
35
+ }
36
+ }
37
+ if (seen >= max)
38
+ continue;
39
+ out += s[i];
40
+ seen++;
41
+ }
42
+ return out;
43
+ }
44
+ /** One shaded row of the input block: accent bar + content padded to width,
45
+ * with the block background re-applied after any color reset inside. */
46
+ function boxRow(content, w) {
47
+ const inner = Math.max(1, w - 2);
48
+ if (visLen(content) > inner)
49
+ content = truncateVisible(content, inner);
50
+ const pad = Math.max(0, inner - visLen(content));
51
+ const body = (content + " ".repeat(pad)).split(RESET).join(RESET + BOX_BG);
52
+ return `${BOX_BG}${ACCENT}▌\x1b[39m ${body}${RESET}`;
53
+ }
54
+ class Tui {
55
+ slashCommands = [];
56
+ getStatus = () => "";
57
+ onModeCycle = null;
58
+ onCancel = null;
59
+ onExit = null;
60
+ placeholder = 'Ask anything… "add a dark mode toggle"';
61
+ /** Shown dim on the left of the hint row (the workspace path). */
62
+ hintLeft = "";
63
+ ed = new editor_1.LineEditor();
64
+ decoder = new keys_1.KeyDecoder();
65
+ state = "hidden";
66
+ prevLines = 0;
67
+ offsetFromBottom = 0;
68
+ history = [];
69
+ histIdx = -1;
70
+ histStash = "";
71
+ menuIndex = 0;
72
+ lastMenuFilter = null;
73
+ notice = null;
74
+ lastCtrlC = 0;
75
+ submitResolve = null;
76
+ sel = null;
77
+ confirmState = null;
78
+ spinnerTimer = null;
79
+ spinnerActive = false;
80
+ atLineStart = true;
81
+ lastKind = null;
82
+ start() {
83
+ process.stdin.setRawMode?.(true);
84
+ process.stdin.resume();
85
+ process.stdin.setEncoding("utf8");
86
+ process.stdin.on("data", (d) => this.onData(d));
87
+ process.stdout.on("resize", () => {
88
+ if (this.state !== "hidden")
89
+ this.redraw();
90
+ });
91
+ process.stdout.write("\x1b[?2004h"); // bracketed paste on
92
+ }
93
+ close() {
94
+ this.stopSpinner();
95
+ this.hideFrame();
96
+ process.stdout.write("\x1b[?2004l\x1b[?25h");
97
+ process.stdin.setRawMode?.(false);
98
+ process.stdin.pause();
99
+ }
100
+ // ---- input ---------------------------------------------------------------
101
+ readInput() {
102
+ this.ed.clear();
103
+ this.histIdx = -1;
104
+ this.menuIndex = 0;
105
+ this.state = "idle";
106
+ this.redraw();
107
+ return new Promise((res) => (this.submitResolve = res));
108
+ }
109
+ select(title, options) {
110
+ this.stopSpinner();
111
+ this.hideFrame();
112
+ process.stdout.write("\x1b[?25l");
113
+ this.state = "select";
114
+ return new Promise((resolve) => {
115
+ this.sel = { title, options, filter: "", index: 0, resolve };
116
+ this.redraw();
117
+ });
118
+ }
119
+ confirmCommand(command, reason) {
120
+ this.stopSpinner();
121
+ this.hideFrame();
122
+ this.state = "confirm";
123
+ return new Promise((resolve) => {
124
+ this.confirmState = { command, reason, resolve };
125
+ this.redraw();
126
+ });
127
+ }
128
+ // ---- key routing ---------------------------------------------------------
129
+ onData(data) {
130
+ for (const key of this.decoder.decode(data)) {
131
+ switch (this.state) {
132
+ case "idle":
133
+ this.keyIdle(key);
134
+ break;
135
+ case "select":
136
+ this.keySelect(key);
137
+ break;
138
+ case "confirm":
139
+ this.keyConfirm(key);
140
+ break;
141
+ case "hidden": // agent running
142
+ if (key.type === "esc" || key.type === "ctrlc")
143
+ this.onCancel?.();
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ keyIdle(key) {
149
+ this.notice = null;
150
+ const menu = this.menuEntries();
151
+ switch (key.type) {
152
+ case "char":
153
+ case "text":
154
+ this.ed.insert(key.text);
155
+ break;
156
+ case "enter":
157
+ this.submit(menu);
158
+ return;
159
+ case "tab":
160
+ if (menu.length > 0) {
161
+ this.ed.set("/" + menu[Math.min(this.menuIndex, menu.length - 1)].name + " ");
162
+ }
163
+ break;
164
+ case "shifttab":
165
+ this.onModeCycle?.();
166
+ break;
167
+ case "backspace":
168
+ this.ed.backspace();
169
+ break;
170
+ case "delete":
171
+ this.ed.del();
172
+ break;
173
+ case "left":
174
+ this.ed.left();
175
+ break;
176
+ case "right":
177
+ this.ed.right();
178
+ break;
179
+ case "home":
180
+ case "ctrla":
181
+ this.ed.home();
182
+ break;
183
+ case "end":
184
+ case "ctrle":
185
+ this.ed.end();
186
+ break;
187
+ case "ctrlu":
188
+ this.ed.killToLineStart();
189
+ break;
190
+ case "ctrlw":
191
+ this.ed.deleteWordBack();
192
+ break;
193
+ case "up":
194
+ if (menu.length > 0) {
195
+ this.menuIndex = (this.menuIndex - 1 + menu.length) % menu.length;
196
+ }
197
+ else if (!this.ed.upLine()) {
198
+ this.historyPrev();
199
+ }
200
+ break;
201
+ case "down":
202
+ if (menu.length > 0) {
203
+ this.menuIndex = (this.menuIndex + 1) % menu.length;
204
+ }
205
+ else if (!this.ed.downLine()) {
206
+ this.historyNext();
207
+ }
208
+ break;
209
+ case "esc":
210
+ this.ed.clear();
211
+ break;
212
+ case "ctrlc": {
213
+ if (this.ed.buffer.length > 0) {
214
+ this.ed.clear();
215
+ }
216
+ else if (Date.now() - this.lastCtrlC < 1500) {
217
+ this.onExit?.();
218
+ return;
219
+ }
220
+ else {
221
+ this.lastCtrlC = Date.now();
222
+ this.notice = "press ctrl+c again to exit";
223
+ }
224
+ break;
225
+ }
226
+ case "ctrld":
227
+ if (this.ed.buffer.length === 0) {
228
+ this.onExit?.();
229
+ return;
230
+ }
231
+ break;
232
+ }
233
+ this.redraw();
234
+ }
235
+ submit(menu) {
236
+ let text = this.ed.buffer;
237
+ if (menu.length > 0) {
238
+ text = "/" + menu[Math.min(this.menuIndex, menu.length - 1)].name;
239
+ }
240
+ text = text.trim();
241
+ if (!text)
242
+ return;
243
+ if (this.history[this.history.length - 1] !== text)
244
+ this.history.push(text);
245
+ this.hideFrame();
246
+ this.state = "hidden";
247
+ // Echo the user's message as an accent-barred block, opencode-style.
248
+ const block = text
249
+ .split("\n")
250
+ .map((l) => `${ACCENT}▌${RESET} ${util_1.c.bold(l)}`)
251
+ .join("\n");
252
+ process.stdout.write(`\n${block}\n\n`);
253
+ this.atLineStart = true;
254
+ this.lastKind = null;
255
+ const resolve = this.submitResolve;
256
+ this.submitResolve = null;
257
+ resolve?.(text);
258
+ }
259
+ historyPrev() {
260
+ if (this.history.length === 0)
261
+ return;
262
+ if (this.histIdx === -1) {
263
+ this.histStash = this.ed.buffer;
264
+ this.histIdx = this.history.length - 1;
265
+ }
266
+ else if (this.histIdx > 0) {
267
+ this.histIdx--;
268
+ }
269
+ else
270
+ return;
271
+ this.ed.set(this.history[this.histIdx]);
272
+ }
273
+ historyNext() {
274
+ if (this.histIdx === -1)
275
+ return;
276
+ if (this.histIdx < this.history.length - 1) {
277
+ this.histIdx++;
278
+ this.ed.set(this.history[this.histIdx]);
279
+ }
280
+ else {
281
+ this.histIdx = -1;
282
+ this.ed.set(this.histStash);
283
+ }
284
+ }
285
+ keySelect(key) {
286
+ const s = this.sel;
287
+ const filtered = this.filteredOptions();
288
+ switch (key.type) {
289
+ case "up":
290
+ s.index = filtered.length ? (s.index - 1 + filtered.length) % filtered.length : 0;
291
+ break;
292
+ case "down":
293
+ case "tab":
294
+ s.index = filtered.length ? (s.index + 1) % filtered.length : 0;
295
+ break;
296
+ case "char":
297
+ case "text":
298
+ s.filter += key.text;
299
+ s.index = 0;
300
+ break;
301
+ case "backspace":
302
+ s.filter = s.filter.slice(0, -1);
303
+ s.index = 0;
304
+ break;
305
+ case "enter": {
306
+ if (!filtered.length)
307
+ break;
308
+ const original = s.options.indexOf(filtered[Math.min(s.index, filtered.length - 1)]);
309
+ this.endSelect(original);
310
+ return;
311
+ }
312
+ case "esc":
313
+ case "ctrlc":
314
+ this.endSelect(null);
315
+ return;
316
+ default:
317
+ break;
318
+ }
319
+ this.redraw();
320
+ }
321
+ endSelect(result) {
322
+ const s = this.sel;
323
+ this.hideFrame();
324
+ process.stdout.write("\x1b[?25h");
325
+ this.sel = null;
326
+ this.state = "hidden";
327
+ s.resolve(result);
328
+ }
329
+ filteredOptions() {
330
+ const s = this.sel;
331
+ if (!s.filter)
332
+ return s.options;
333
+ const f = s.filter.toLowerCase();
334
+ return s.options.filter((o) => o.label.toLowerCase().includes(f));
335
+ }
336
+ keyConfirm(key) {
337
+ const cs = this.confirmState;
338
+ let result = null;
339
+ if (key.type === "char") {
340
+ const ch = key.text.toLowerCase();
341
+ if (ch === "y")
342
+ result = "yes";
343
+ else if (ch === "n")
344
+ result = "no";
345
+ else if (ch === "a")
346
+ result = "always";
347
+ }
348
+ else if (key.type === "enter")
349
+ result = "yes";
350
+ else if (key.type === "esc" || key.type === "ctrlc")
351
+ result = "no";
352
+ if (result === null)
353
+ return;
354
+ this.hideFrame();
355
+ this.confirmState = null;
356
+ this.state = "hidden";
357
+ process.stdout.write(util_1.c.dim(` ${result === "always" ? "always allowed" : result} — ${cs.command}\n`));
358
+ cs.resolve(result);
359
+ }
360
+ // ---- rendering -----------------------------------------------------------
361
+ menuEntries() {
362
+ const b = this.ed.buffer;
363
+ if (!b.startsWith("/") || b.includes(" ") || b.includes("\n"))
364
+ return [];
365
+ const filter = b.slice(1).toLowerCase();
366
+ const list = this.slashCommands.filter((cmd) => cmd.name.startsWith(filter)).slice(0, 8);
367
+ if (filter !== this.lastMenuFilter) {
368
+ this.menuIndex = 0;
369
+ this.lastMenuFilter = filter;
370
+ }
371
+ if (this.menuIndex >= list.length)
372
+ this.menuIndex = 0;
373
+ return list;
374
+ }
375
+ width() {
376
+ return Math.max(30, (process.stdout.columns || 80) - 1);
377
+ }
378
+ redraw() {
379
+ const lines = [];
380
+ let cursorRow = -1;
381
+ let cursorCol = 0;
382
+ if (this.state === "idle") {
383
+ const w = this.width();
384
+ const menu = this.menuEntries();
385
+ const menuW = Math.min(w, 64);
386
+ for (let i = 0; i < menu.length; i++) {
387
+ const row = ` /${menu[i].name.padEnd(12)} ${menu[i].desc}`.slice(0, menuW).padEnd(menuW);
388
+ lines.push(i === this.menuIndex
389
+ ? `${SEL}${row}${RESET}`
390
+ : ` ${util_1.c.bold("/" + menu[i].name.padEnd(12))} ${util_1.c.dim(menu[i].desc)}`);
391
+ }
392
+ const inputW = w - 2;
393
+ lines.push(boxRow("", w)); // top padding
394
+ if (this.ed.buffer.length === 0) {
395
+ cursorRow = lines.length;
396
+ cursorCol = 2;
397
+ lines.push(boxRow(util_1.c.dim(this.placeholder.slice(0, inputW)), w));
398
+ }
399
+ else {
400
+ const lay = (0, editor_1.layoutBuffer)(this.ed.buffer, this.ed.cursor, inputW);
401
+ cursorRow = lines.length + lay.curRow;
402
+ cursorCol = 2 + lay.curCol;
403
+ for (const row of lay.rows)
404
+ lines.push(boxRow(row, w));
405
+ }
406
+ lines.push(boxRow("", w)); // spacer
407
+ lines.push(boxRow(this.getStatus(), w));
408
+ lines.push(boxRow("", w)); // bottom padding
409
+ if (this.notice) {
410
+ lines.push(" " + util_1.c.yellow(this.notice));
411
+ }
412
+ else {
413
+ const keys = "/ commands · shift+tab mode";
414
+ const left = this.hintLeft
415
+ ? this.hintLeft.length + keys.length + 5 > w
416
+ ? "…" + this.hintLeft.slice(-(w - keys.length - 6))
417
+ : this.hintLeft
418
+ : "";
419
+ lines.push(util_1.c.dim(` ${left}${left ? " " : ""}${keys}`));
420
+ }
421
+ }
422
+ else if (this.state === "select" && this.sel) {
423
+ const s = this.sel;
424
+ const w = Math.min(this.width(), 64);
425
+ lines.push(BAR + util_1.c.bold(s.title) + " " + util_1.c.dim("esc cancel"));
426
+ lines.push(BAR + (s.filter ? s.filter : util_1.c.dim("type to filter")));
427
+ const filtered = this.filteredOptions();
428
+ if (!filtered.length)
429
+ lines.push(util_1.c.dim(" no matches"));
430
+ for (let i = 0; i < Math.min(filtered.length, 10); i++) {
431
+ const o = filtered[i];
432
+ const marker = o.current ? "● " : " ";
433
+ const plain = ` ${marker}${o.label}${o.hint ? " " + o.hint : ""}`.slice(0, w).padEnd(w);
434
+ lines.push(i === s.index
435
+ ? `${SEL}${plain}${RESET}`
436
+ : ` ${o.current ? util_1.c.green(marker) : marker}${o.label}${o.hint ? " " + util_1.c.dim(o.hint) : ""}`);
437
+ }
438
+ if (filtered.length > 10)
439
+ lines.push(util_1.c.dim(` … ${filtered.length - 10} more (type to filter)`));
440
+ }
441
+ else if (this.state === "confirm" && this.confirmState) {
442
+ lines.push(BAR + util_1.c.yellow("run? ") + util_1.c.bold(this.confirmState.command.slice(0, this.width() - 8)));
443
+ if (this.confirmState.reason)
444
+ lines.push(" " + util_1.c.dim(this.confirmState.reason));
445
+ const program = this.confirmState.command.trim().split(/\s+/)[0];
446
+ lines.push(" " + util_1.c.dim(`[y]es · [n]o · [a]lways allow '${program}' this session`));
447
+ }
448
+ else {
449
+ return;
450
+ }
451
+ this.writeFrame(lines, cursorRow, cursorCol);
452
+ }
453
+ writeFrame(lines, cursorRow, cursorCol) {
454
+ let seq = "";
455
+ if (this.prevLines > 0) {
456
+ if (this.offsetFromBottom > 0)
457
+ seq += `\x1b[${this.offsetFromBottom}B`;
458
+ seq += "\r";
459
+ if (this.prevLines > 1)
460
+ seq += `\x1b[${this.prevLines - 1}A`;
461
+ seq += "\x1b[J";
462
+ }
463
+ seq += lines.join("\n");
464
+ // park the cursor
465
+ if (cursorRow >= 0 && cursorRow < lines.length) {
466
+ const up = lines.length - 1 - cursorRow;
467
+ if (up > 0)
468
+ seq += `\x1b[${up}A`;
469
+ seq += "\r";
470
+ if (cursorCol > 0)
471
+ seq += `\x1b[${cursorCol}C`;
472
+ this.offsetFromBottom = up;
473
+ }
474
+ else {
475
+ this.offsetFromBottom = 0;
476
+ }
477
+ process.stdout.write(seq);
478
+ this.prevLines = lines.length;
479
+ }
480
+ hideFrame() {
481
+ if (this.prevLines === 0)
482
+ return;
483
+ let seq = "";
484
+ if (this.offsetFromBottom > 0)
485
+ seq += `\x1b[${this.offsetFromBottom}B`;
486
+ seq += "\r";
487
+ if (this.prevLines > 1)
488
+ seq += `\x1b[${this.prevLines - 1}A`;
489
+ seq += "\x1b[J";
490
+ process.stdout.write(seq);
491
+ this.prevLines = 0;
492
+ this.offsetFromBottom = 0;
493
+ }
494
+ /** Redraw the frame if one is on screen (status bar refresh, etc.). */
495
+ refresh() {
496
+ if (this.state !== "hidden")
497
+ this.redraw();
498
+ }
499
+ // ---- AgentUI (output while the agent runs) -------------------------------
500
+ out(s) {
501
+ if (this.tickerOn)
502
+ this.endTicker();
503
+ if (this.state !== "hidden") {
504
+ this.hideFrame();
505
+ process.stdout.write(s);
506
+ this.redraw();
507
+ }
508
+ else {
509
+ process.stdout.write(s);
510
+ }
511
+ }
512
+ thinkBuf = "";
513
+ thinkStart = 0;
514
+ tickerOn = false;
515
+ ensureLine() {
516
+ if (this.tickerOn) {
517
+ this.endTicker();
518
+ return;
519
+ }
520
+ if (!this.atLineStart) {
521
+ this.out("\n");
522
+ this.atLineStart = true;
523
+ }
524
+ }
525
+ token(text) {
526
+ this.stopSpinner();
527
+ this.lastKind = "content";
528
+ this.out(text);
529
+ this.atLineStart = text.endsWith("\n");
530
+ }
531
+ /** Reasoning streams as ONE grey line, cropped to the latest tail and
532
+ * overwritten in place — a live pulse, not a wall of text. It collapses to
533
+ * "✦ thought for Ns" the moment real output starts. */
534
+ thinking(text) {
535
+ this.stopSpinner();
536
+ if (this.state !== "hidden")
537
+ return;
538
+ if (!this.tickerOn) {
539
+ if (!this.atLineStart)
540
+ this.out("\n");
541
+ this.tickerOn = true;
542
+ this.thinkStart = Date.now();
543
+ this.thinkBuf = "";
544
+ }
545
+ this.thinkBuf += text;
546
+ if (this.thinkBuf.length > 4000)
547
+ this.thinkBuf = this.thinkBuf.slice(-2000);
548
+ const w = Math.max(20, (process.stdout.columns || 80) - 4);
549
+ const clean = this.thinkBuf.replace(/\s+/g, " ").trim();
550
+ const tail = clean.length > w ? "…" + clean.slice(-(w - 1)) : clean;
551
+ process.stdout.write("\r\x1b[2K" + util_1.c.gray("✦ " + tail));
552
+ this.atLineStart = false;
553
+ }
554
+ endTicker() {
555
+ if (!this.tickerOn)
556
+ return;
557
+ this.tickerOn = false;
558
+ const secs = ((Date.now() - this.thinkStart) / 1000).toFixed(1);
559
+ process.stdout.write("\r\x1b[2K" + util_1.c.gray(`✦ thought for ${secs}s`) + "\n");
560
+ this.thinkBuf = "";
561
+ this.atLineStart = true;
562
+ }
563
+ toolCall(name, args) {
564
+ this.stopSpinner();
565
+ this.ensureLine();
566
+ this.lastKind = null;
567
+ this.out(`${util_1.c.cyan("→")} ${util_1.c.bold(name)} ${util_1.c.dim((0, ui_1.summarizeArgs)(name, args))}\n`);
568
+ }
569
+ toolResult(result) {
570
+ this.ensureLine();
571
+ const firstLine = result.split("\n")[0] ?? "";
572
+ const isError = firstLine.startsWith("Error");
573
+ const lineCount = result.split("\n").length;
574
+ const label = isError
575
+ ? util_1.c.red(firstLine.slice(0, 120))
576
+ : util_1.c.dim(firstLine.slice(0, 100) + (lineCount > 1 ? ` (+${lineCount - 1} lines)` : ""));
577
+ this.out(` ${isError ? util_1.c.red("✗") : util_1.c.green("✓")} ${label}\n`);
578
+ }
579
+ println(s = "") {
580
+ this.stopSpinner();
581
+ this.ensureLine();
582
+ this.lastKind = null;
583
+ this.out(s + "\n");
584
+ this.atLineStart = true;
585
+ }
586
+ status(s) {
587
+ this.println(util_1.c.gray(s));
588
+ }
589
+ turnEnd(label) {
590
+ this.ensureLine();
591
+ this.lastKind = null;
592
+ this.out(`${util_1.c.dim("■ " + label)}\n\n`);
593
+ this.atLineStart = true;
594
+ }
595
+ planUpdated(plan) {
596
+ this.stopSpinner();
597
+ this.ensureLine();
598
+ this.lastKind = null;
599
+ this.out((0, ui_1.renderPlan)(plan) + "\n");
600
+ this.atLineStart = true;
601
+ }
602
+ warn(s) {
603
+ this.println(util_1.c.yellow(s));
604
+ }
605
+ error(s) {
606
+ this.println(util_1.c.red(s));
607
+ }
608
+ startSpinner(label) {
609
+ if (!process.stdout.isTTY || this.state !== "hidden")
610
+ return;
611
+ this.stopSpinner();
612
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
613
+ let i = 0;
614
+ const started = Date.now();
615
+ this.spinnerActive = true;
616
+ this.spinnerTimer = setInterval(() => {
617
+ const secs = Math.floor((Date.now() - started) / 1000);
618
+ process.stdout.write(`\r${util_1.c.cyan(frames[i++ % frames.length])} ${util_1.c.dim(label + (secs > 2 ? ` ${secs}s` : "") + " · esc to cancel")} `);
619
+ }, 100);
620
+ }
621
+ stopSpinner() {
622
+ if (this.spinnerTimer) {
623
+ clearInterval(this.spinnerTimer);
624
+ this.spinnerTimer = null;
625
+ }
626
+ if (this.spinnerActive) {
627
+ process.stdout.write("\r" + " ".repeat(70) + "\r");
628
+ this.spinnerActive = false;
629
+ }
630
+ }
631
+ }
632
+ exports.Tui = Tui;