@lazyingart/agintiflow 0.12.1 → 0.12.2

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 CHANGED
@@ -67,7 +67,7 @@ aginti
67
67
  aginti chat
68
68
  ```
69
69
 
70
- Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, while Enter sends the message. Assistant responses render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, and tables. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
70
+ Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. Assistant responses render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, and tables. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
71
71
 
72
72
  For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
73
73
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
6
6
  "license": "Apache-2.0",
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { stripMarkdown } from "../src/interactive-cli.js";
7
+ import { buildPromptLayout, stripMarkdown } from "../src/interactive-cli.js";
8
8
 
9
9
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
10
  const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-"));
@@ -70,6 +70,21 @@ try {
70
70
  throw new Error("terminal markdown renderer dropped table content");
71
71
  }
72
72
 
73
+ const promptLayout = buildPromptLayout(`${"x".repeat(180)}\nsecond line`, 95, 80, 24);
74
+ const visibleLengths = promptLayout.renderedRows.map((line) =>
75
+ line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").length
76
+ );
77
+ if (promptLayout.rows.length < 4 || Math.max(...visibleLengths) > 79) {
78
+ throw new Error("terminal prompt layout did not wrap long multiline input safely");
79
+ }
80
+ if (promptLayout.cursorRow < 0 || promptLayout.cursorColumn < 0) {
81
+ throw new Error("terminal prompt layout returned an invalid cursor location");
82
+ }
83
+ const hugePromptLayout = buildPromptLayout(Array.from({ length: 30 }, (_unused, index) => `line ${index + 1}`).join("\n"), 120, 80, 20);
84
+ if (hugePromptLayout.renderedRows.length > 12 || !hugePromptLayout.renderedRows.some((line) => line.includes("earlier input row"))) {
85
+ throw new Error("terminal prompt layout did not bound redraw size for large prompts");
86
+ }
87
+
73
88
  const result = await runChat("Create notes/interactive.md with a short CLI chat smoke message\n/exit\n");
74
89
  const written = await fs.readFile(path.join(tempRoot, "notes/interactive.md"), "utf8");
75
90
  if (!written.includes("Created by AgInTiFlow mock mode.")) {
@@ -92,7 +107,7 @@ try {
92
107
  {
93
108
  ok: true,
94
109
  projectRoot: tempRoot,
95
- checks: ["markdown-render", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
110
+ checks: ["markdown-render", "prompt-layout", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
96
111
  },
97
112
  null,
98
113
  2
@@ -53,6 +53,7 @@ const SLASH_COMMANDS = [
53
53
  "/web",
54
54
  "/exit",
55
55
  ];
56
+ const promptHistory = [];
56
57
 
57
58
  function color(value, ...codes) {
58
59
  if (!useColor || codes.length === 0) return String(value);
@@ -71,6 +72,18 @@ function terminalWidth() {
71
72
  return Math.max(Number(output.columns) || 80, 40);
72
73
  }
73
74
 
75
+ function terminalHeight() {
76
+ return Math.max(Number(output.rows) || 24, 10);
77
+ }
78
+
79
+ function editorWidth(width = terminalWidth()) {
80
+ return Math.max(Number(width) - 1, 39);
81
+ }
82
+
83
+ function promptViewportRows(height = terminalHeight()) {
84
+ return Math.max(Math.min(Math.floor(Number(height) * 0.42), 10), 4);
85
+ }
86
+
74
87
  function visualLength(value) {
75
88
  return stripAnsi(value).length;
76
89
  }
@@ -80,10 +93,11 @@ function padVisible(value, width) {
80
93
  return `${value}${" ".repeat(padding)}`;
81
94
  }
82
95
 
83
- function panelLine(content = "", bgCode = ansi.systemBg) {
84
- const width = terminalWidth();
85
- if (!useColor) return padVisible(content, width);
86
- const padded = padVisible(content, width).replaceAll(ansi.reset, `${ansi.reset}${bgCode}`);
96
+ function panelLine(content = "", bgCode = ansi.systemBg, width = editorWidth()) {
97
+ const raw = String(content || "");
98
+ const safeContent = visualLength(raw) > width ? stripAnsi(raw).slice(0, width) : raw;
99
+ if (!useColor) return padVisible(safeContent, width);
100
+ const padded = padVisible(safeContent, width).replaceAll(ansi.reset, `${ansi.reset}${bgCode}`);
87
101
  return `${bgCode}${padded}${ansi.reset}`;
88
102
  }
89
103
 
@@ -113,6 +127,10 @@ function commandSuggestions(line = "") {
113
127
  return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
114
128
  }
115
129
 
130
+ function clamp(value, min, max) {
131
+ return Math.min(Math.max(value, min), max);
132
+ }
133
+
116
134
  export function stripMarkdown(text) {
117
135
  const lines = String(text || "").split(/\r?\n/);
118
136
  let inFence = false;
@@ -344,29 +362,236 @@ function printHelp() {
344
362
  );
345
363
  }
346
364
 
347
- function renderPromptBuffer(buffer, previousLineCount = 0) {
348
- for (let index = 0; index < previousLineCount; index += 1) {
349
- output.write(`\r${ansi.clearLine}`);
350
- if (index < previousLineCount - 1) output.write("\x1b[1A");
365
+ function logicalLinesWithOffsets(buffer = "") {
366
+ const lines = String(buffer).split("\n");
367
+ let offset = 0;
368
+ return lines.map((line, index) => {
369
+ const start = offset;
370
+ const end = start + line.length;
371
+ offset = end + 1;
372
+ return {
373
+ text: line,
374
+ start,
375
+ end,
376
+ hasNewline: index < lines.length - 1,
377
+ };
378
+ });
379
+ }
380
+
381
+ function promptVisibleWindow(rows, cursorRow, height = terminalHeight()) {
382
+ const maxRows = promptViewportRows(height);
383
+ if (rows.length <= maxRows) {
384
+ return { start: 0, end: rows.length, topHidden: 0, bottomHidden: 0 };
351
385
  }
386
+ const half = Math.floor(maxRows / 2);
387
+ const start = clamp(cursorRow - half, 0, rows.length - maxRows);
388
+ const end = start + maxRows;
389
+ return {
390
+ start,
391
+ end,
392
+ topHidden: start,
393
+ bottomHidden: rows.length - end,
394
+ };
395
+ }
352
396
 
353
- const lines = String(buffer || "").split("\n");
354
- const suggestions = commandSuggestions(lines[0] || "");
355
- const rendered = [];
397
+ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth(), height = terminalHeight()) {
398
+ const safeBuffer = String(buffer || "");
399
+ const safeCursor = clamp(Number(cursor) || 0, 0, safeBuffer.length);
400
+ const lineWidth = editorWidth(width);
356
401
  const firstPrefix = " user ";
357
402
  const nextPrefix = " ... ";
358
- const emptyHint = color("type a request, /help, Enter to send, Ctrl+J for newline", ansi.faint);
359
- const cursor = color("▌", ansi.bold);
360
- rendered.push(panelLine(`${firstPrefix}${lines[0] || emptyHint}${lines.length === 1 ? cursor : ""}`, ansi.userBg));
361
- for (const [index, line] of lines.slice(1).entries()) {
362
- const isLast = index === lines.length - 2;
363
- rendered.push(panelLine(`${nextPrefix}${line}${isLast ? cursor : ""}`, ansi.userBg));
403
+ const firstInnerWidth = Math.max(lineWidth - firstPrefix.length, 8);
404
+ const nextInnerWidth = Math.max(lineWidth - nextPrefix.length, 8);
405
+ const rows = [];
406
+
407
+ for (const [lineIndex, line] of logicalLinesWithOffsets(safeBuffer).entries()) {
408
+ let localOffset = 0;
409
+ const text = line.text;
410
+ if (!text) {
411
+ rows.push({
412
+ prefix: lineIndex === 0 ? firstPrefix : nextPrefix,
413
+ text: "",
414
+ start: line.start,
415
+ end: line.start,
416
+ innerWidth: lineIndex === 0 ? firstInnerWidth : nextInnerWidth,
417
+ lineStart: line.start,
418
+ lineEnd: line.end,
419
+ hasNewline: line.hasNewline,
420
+ });
421
+ continue;
422
+ }
423
+
424
+ while (localOffset < text.length) {
425
+ const prefix = lineIndex === 0 && localOffset === 0 ? firstPrefix : nextPrefix;
426
+ const innerWidth = prefix === firstPrefix ? firstInnerWidth : nextInnerWidth;
427
+ const chunk = text.slice(localOffset, localOffset + innerWidth);
428
+ rows.push({
429
+ prefix,
430
+ text: chunk,
431
+ start: line.start + localOffset,
432
+ end: line.start + localOffset + chunk.length,
433
+ innerWidth,
434
+ lineStart: line.start,
435
+ lineEnd: line.end,
436
+ hasNewline: line.hasNewline,
437
+ });
438
+ localOffset += chunk.length;
439
+ }
440
+ }
441
+
442
+ if (rows.length === 0) {
443
+ rows.push({
444
+ prefix: firstPrefix,
445
+ text: "",
446
+ start: 0,
447
+ end: 0,
448
+ innerWidth: firstInnerWidth,
449
+ lineStart: 0,
450
+ lineEnd: 0,
451
+ hasNewline: false,
452
+ });
453
+ }
454
+
455
+ const last = rows[rows.length - 1];
456
+ if (safeCursor === safeBuffer.length && last.end === safeCursor && last.text.length >= last.innerWidth) {
457
+ rows.push({
458
+ prefix: nextPrefix,
459
+ text: "",
460
+ start: safeCursor,
461
+ end: safeCursor,
462
+ innerWidth: nextInnerWidth,
463
+ lineStart: safeCursor,
464
+ lineEnd: safeCursor,
465
+ hasNewline: false,
466
+ });
364
467
  }
468
+
469
+ let cursorRow = rows.length - 1;
470
+ let cursorColumn = rows[cursorRow].prefix.length;
471
+ for (let index = 0; index < rows.length; index += 1) {
472
+ const row = rows[index];
473
+ const next = rows[index + 1];
474
+ if (safeCursor < row.end) {
475
+ cursorRow = index;
476
+ cursorColumn = row.prefix.length + safeCursor - row.start;
477
+ break;
478
+ }
479
+ if (safeCursor === row.end) {
480
+ if (next && next.start === safeCursor && row.text.length >= row.innerWidth) continue;
481
+ cursorRow = index;
482
+ cursorColumn = row.prefix.length + safeCursor - row.start;
483
+ break;
484
+ }
485
+ }
486
+
487
+ const suggestions = commandSuggestions(safeBuffer.split("\n")[0] || "");
488
+ const emptyHint = "type a request, /help, Enter to send, Ctrl+J for newline";
489
+ const visible = promptVisibleWindow(rows, cursorRow, height);
490
+ const renderedRows = [];
491
+ let renderedCursorRow = cursorRow - visible.start;
492
+
493
+ if (visible.topHidden > 0) {
494
+ renderedRows.push(panelLine(` ... ${visible.topHidden} earlier input row${visible.topHidden === 1 ? "" : "s"}`, ansi.systemBg, lineWidth));
495
+ renderedCursorRow += 1;
496
+ }
497
+
498
+ for (const row of rows.slice(visible.start, visible.end)) {
499
+ const content = safeBuffer ? `${row.prefix}${row.text}` : `${row.prefix}${emptyHint}`;
500
+ renderedRows.push(panelLine(content, ansi.userBg, lineWidth));
501
+ }
502
+
503
+ if (visible.bottomHidden > 0) {
504
+ renderedRows.push(panelLine(` ... ${visible.bottomHidden} later input row${visible.bottomHidden === 1 ? "" : "s"}`, ansi.systemBg, lineWidth));
505
+ }
506
+
365
507
  if (suggestions.length > 0) {
366
- rendered.push(panelLine(` hint ${color(suggestions.join(" "), ansi.dim)}`, ansi.systemBg));
508
+ renderedRows.push(panelLine(` hint ${suggestions.join(" ")}`, ansi.systemBg, lineWidth));
509
+ }
510
+
511
+ return {
512
+ rows,
513
+ renderedRows,
514
+ cursorRow: renderedCursorRow,
515
+ absoluteCursorRow: cursorRow,
516
+ cursorColumn: clamp(cursorColumn, 0, editorWidth(width) - 1),
517
+ };
518
+ }
519
+
520
+ function cursorLocation(layout, cursor) {
521
+ for (let index = 0; index < layout.rows.length; index += 1) {
522
+ const row = layout.rows[index];
523
+ const next = layout.rows[index + 1];
524
+ if (cursor < row.end) return { rowIndex: index, column: cursor - row.start };
525
+ if (cursor === row.end) {
526
+ if (next && next.start === cursor && row.text.length >= row.innerWidth) continue;
527
+ return { rowIndex: index, column: cursor - row.start };
528
+ }
529
+ }
530
+ const rowIndex = Math.max(layout.rows.length - 1, 0);
531
+ const row = layout.rows[rowIndex];
532
+ return { rowIndex, column: Math.max(row.end - row.start, 0) };
533
+ }
534
+
535
+ function clearRenderedPrompt(previous) {
536
+ if (!previous.lineCount) return;
537
+ const below = previous.lineCount - 1 - previous.cursorRow;
538
+ if (below > 0) output.write(`\x1b[${below}B`);
539
+ output.write(`\r${ansi.clearLine}`);
540
+ for (let index = 1; index < previous.lineCount; index += 1) {
541
+ output.write(`\x1b[1A\r${ansi.clearLine}`);
367
542
  }
368
- output.write(rendered.join("\n"));
369
- return rendered.length;
543
+ }
544
+
545
+ function renderPromptBuffer(buffer, cursor, previous = { lineCount: 0, cursorRow: 0 }) {
546
+ output.write(ansi.cursorHide);
547
+ clearRenderedPrompt(previous);
548
+ const layout = buildPromptLayout(buffer, cursor);
549
+ output.write(layout.renderedRows.join("\n"));
550
+ const below = layout.renderedRows.length - 1 - layout.cursorRow;
551
+ if (below > 0) output.write(`\x1b[${below}A`);
552
+ output.write(`\r\x1b[${layout.cursorColumn + 1}G`);
553
+ output.write(ansi.cursorShow);
554
+ return {
555
+ lineCount: layout.renderedRows.length,
556
+ cursorRow: layout.cursorRow,
557
+ };
558
+ }
559
+
560
+ function moveToPromptBottom(rendered) {
561
+ const below = Math.max((rendered?.lineCount || 1) - 1 - (rendered?.cursorRow || 0), 0);
562
+ if (below > 0) output.write(`\x1b[${below}B`);
563
+ output.write("\r");
564
+ }
565
+
566
+ function lineBounds(buffer, cursor) {
567
+ const safeCursor = clamp(cursor, 0, buffer.length);
568
+ const start = buffer.lastIndexOf("\n", safeCursor - 1) + 1;
569
+ const nextNewline = buffer.indexOf("\n", safeCursor);
570
+ const end = nextNewline === -1 ? buffer.length : nextNewline;
571
+ return { start, end };
572
+ }
573
+
574
+ function insertAt(buffer, cursor, text) {
575
+ return {
576
+ buffer: `${buffer.slice(0, cursor)}${text}${buffer.slice(cursor)}`,
577
+ cursor: cursor + text.length,
578
+ };
579
+ }
580
+
581
+ function removeBefore(buffer, cursor) {
582
+ if (cursor <= 0) return { buffer, cursor };
583
+ return {
584
+ buffer: `${buffer.slice(0, cursor - 1)}${buffer.slice(cursor)}`,
585
+ cursor: cursor - 1,
586
+ };
587
+ }
588
+
589
+ function removeAt(buffer, cursor) {
590
+ if (cursor >= buffer.length) return { buffer, cursor };
591
+ return {
592
+ buffer: `${buffer.slice(0, cursor)}${buffer.slice(cursor + 1)}`,
593
+ cursor,
594
+ };
370
595
  }
371
596
 
372
597
  function createAbortError(message = "Aborted with Ctrl+C") {
@@ -381,34 +606,97 @@ function readTtyPrompt() {
381
606
  emitKeypressEvents(input);
382
607
  const wasRaw = Boolean(input.isRaw);
383
608
  let buffer = "";
384
- let renderedLines = 0;
609
+ let cursor = 0;
610
+ let rendered = { lineCount: 0, cursorRow: 0 };
611
+ let preferredColumn = null;
612
+ let historyIndex = promptHistory.length;
613
+ let draft = "";
614
+ let redrawHandle = null;
385
615
 
386
616
  const cleanup = () => {
617
+ if (redrawHandle) {
618
+ clearImmediate(redrawHandle);
619
+ redrawHandle = null;
620
+ }
387
621
  input.off("keypress", handler);
388
622
  if (typeof input.setRawMode === "function") input.setRawMode(wasRaw);
389
623
  input.pause();
390
624
  output.write(ansi.cursorShow);
391
625
  };
392
626
 
627
+ const renderNow = () => {
628
+ if (redrawHandle) {
629
+ clearImmediate(redrawHandle);
630
+ redrawHandle = null;
631
+ }
632
+ rendered = renderPromptBuffer(buffer, cursor, rendered);
633
+ };
634
+
393
635
  const redraw = () => {
394
- renderedLines = renderPromptBuffer(buffer, renderedLines);
636
+ if (redrawHandle) return;
637
+ redrawHandle = setImmediate(() => {
638
+ redrawHandle = null;
639
+ rendered = renderPromptBuffer(buffer, cursor, rendered);
640
+ });
395
641
  };
396
642
 
397
643
  const submit = () => {
644
+ renderNow();
645
+ moveToPromptBottom(rendered);
398
646
  cleanup();
399
647
  output.write("\n");
648
+ const saved = buffer.trim();
649
+ if (saved && promptHistory[promptHistory.length - 1] !== buffer) promptHistory.push(buffer);
400
650
  resolve(buffer);
401
651
  };
402
652
 
653
+ const setBuffer = (nextBuffer, nextCursor = nextBuffer.length) => {
654
+ buffer = nextBuffer;
655
+ cursor = clamp(nextCursor, 0, buffer.length);
656
+ preferredColumn = null;
657
+ redraw();
658
+ };
659
+
660
+ const moveVertical = (direction) => {
661
+ const layout = buildPromptLayout(buffer, cursor);
662
+ const location = cursorLocation(layout, cursor);
663
+ const targetRowIndex = location.rowIndex + direction;
664
+ if (targetRowIndex < 0) {
665
+ if (promptHistory.length === 0) return;
666
+ if (historyIndex === promptHistory.length) draft = buffer;
667
+ historyIndex = Math.max(historyIndex - 1, 0);
668
+ setBuffer(promptHistory[historyIndex], promptHistory[historyIndex].length);
669
+ return;
670
+ }
671
+ if (targetRowIndex >= layout.rows.length) {
672
+ if (historyIndex < promptHistory.length - 1) {
673
+ historyIndex += 1;
674
+ setBuffer(promptHistory[historyIndex], promptHistory[historyIndex].length);
675
+ } else if (historyIndex < promptHistory.length) {
676
+ historyIndex = promptHistory.length;
677
+ setBuffer(draft, draft.length);
678
+ }
679
+ return;
680
+ }
681
+ const currentColumn = preferredColumn ?? location.column;
682
+ const targetRow = layout.rows[targetRowIndex];
683
+ cursor = targetRow.start + Math.min(currentColumn, targetRow.end - targetRow.start);
684
+ preferredColumn = currentColumn;
685
+ redraw();
686
+ };
687
+
403
688
  const handler = (str = "", key = {}) => {
404
689
  if (key.ctrl && key.name === "c") {
690
+ renderNow();
691
+ moveToPromptBottom(rendered);
405
692
  cleanup();
406
693
  output.write("\n");
407
694
  reject(createAbortError());
408
695
  return;
409
696
  }
410
697
  if ((key.ctrl && key.name === "j") || key.sequence === "\n") {
411
- buffer += "\n";
698
+ ({ buffer, cursor } = insertAt(buffer, cursor, "\n"));
699
+ preferredColumn = null;
412
700
  redraw();
413
701
  return;
414
702
  }
@@ -417,7 +705,61 @@ function readTtyPrompt() {
417
705
  return;
418
706
  }
419
707
  if (key.name === "backspace") {
420
- buffer = buffer.slice(0, -1);
708
+ ({ buffer, cursor } = removeBefore(buffer, cursor));
709
+ preferredColumn = null;
710
+ redraw();
711
+ return;
712
+ }
713
+ if (key.name === "delete") {
714
+ ({ buffer, cursor } = removeAt(buffer, cursor));
715
+ preferredColumn = null;
716
+ redraw();
717
+ return;
718
+ }
719
+ if (key.name === "left") {
720
+ cursor = Math.max(cursor - 1, 0);
721
+ preferredColumn = null;
722
+ redraw();
723
+ return;
724
+ }
725
+ if (key.name === "right") {
726
+ cursor = Math.min(cursor + 1, buffer.length);
727
+ preferredColumn = null;
728
+ redraw();
729
+ return;
730
+ }
731
+ if (key.name === "up") {
732
+ moveVertical(-1);
733
+ return;
734
+ }
735
+ if (key.name === "down") {
736
+ moveVertical(1);
737
+ return;
738
+ }
739
+ if ((key.ctrl && key.name === "a") || key.name === "home") {
740
+ cursor = lineBounds(buffer, cursor).start;
741
+ preferredColumn = null;
742
+ redraw();
743
+ return;
744
+ }
745
+ if ((key.ctrl && key.name === "e") || key.name === "end") {
746
+ cursor = lineBounds(buffer, cursor).end;
747
+ preferredColumn = null;
748
+ redraw();
749
+ return;
750
+ }
751
+ if (key.ctrl && key.name === "u") {
752
+ const bounds = lineBounds(buffer, cursor);
753
+ buffer = `${buffer.slice(0, bounds.start)}${buffer.slice(cursor)}`;
754
+ cursor = bounds.start;
755
+ preferredColumn = null;
756
+ redraw();
757
+ return;
758
+ }
759
+ if (key.ctrl && key.name === "k") {
760
+ const bounds = lineBounds(buffer, cursor);
761
+ buffer = `${buffer.slice(0, cursor)}${buffer.slice(bounds.end)}`;
762
+ preferredColumn = null;
421
763
  redraw();
422
764
  return;
423
765
  }
@@ -425,27 +767,32 @@ function readTtyPrompt() {
425
767
  const suggestions = commandSuggestions(buffer.split("\n")[0] || "");
426
768
  if (suggestions.length === 1) {
427
769
  buffer = suggestions[0];
770
+ cursor = buffer.length;
428
771
  }
772
+ preferredColumn = null;
429
773
  redraw();
430
774
  return;
431
775
  }
432
776
  if (key.name === "escape") {
433
777
  buffer = "";
778
+ cursor = 0;
779
+ preferredColumn = null;
434
780
  redraw();
435
781
  return;
436
782
  }
437
783
  if (key.ctrl || key.meta) return;
438
784
  if (str && !key.sequence?.startsWith("\x1b")) {
439
- buffer += str;
785
+ const text = str.replace(/\r/g, "");
786
+ ({ buffer, cursor } = insertAt(buffer, cursor, text));
787
+ preferredColumn = null;
440
788
  redraw();
441
789
  }
442
790
  };
443
791
 
444
792
  input.resume();
445
793
  input.setRawMode(true);
446
- output.write(ansi.cursorHide);
447
794
  input.on("keypress", handler);
448
- redraw();
795
+ renderNow();
449
796
  });
450
797
  }
451
798