@lazyingart/agintiflow 0.8.9 → 0.8.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
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",
package/public/app.js CHANGED
@@ -1014,6 +1014,201 @@ function escapeHtml(value) {
1014
1014
  );
1015
1015
  }
1016
1016
 
1017
+ function safeLinkHref(value) {
1018
+ const raw = String(value || "").trim();
1019
+ try {
1020
+ const parsed = new URL(raw, window.location.origin);
1021
+ return ["http:", "https:", "mailto:"].includes(parsed.protocol) ? parsed.href : "";
1022
+ } catch {
1023
+ return "";
1024
+ }
1025
+ }
1026
+
1027
+ function renderInlineMarkdown(value) {
1028
+ const placeholders = [];
1029
+ const protect = (html) => {
1030
+ const index = placeholders.push(html) - 1;
1031
+ return `\u0000${index}\u0000`;
1032
+ };
1033
+
1034
+ let text = String(value || "");
1035
+ text = text.replace(/`([^`\n]+)`/g, (_match, code) => protect(`<code>${escapeHtml(code)}</code>`));
1036
+ text = text.replace(/\[([^\]\n]+)]\(([^)\s]+)\)/g, (_match, label, href) => {
1037
+ const safeHref = safeLinkHref(href);
1038
+ if (!safeHref) return escapeHtml(label);
1039
+ return protect(
1040
+ `<a href="${escapeHtml(safeHref)}" target="_blank" rel="noopener noreferrer">${escapeHtml(label)}</a>`
1041
+ );
1042
+ });
1043
+
1044
+ let html = escapeHtml(text);
1045
+ html = html
1046
+ .replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>")
1047
+ .replace(/__([^_\n]+)__/g, "<strong>$1</strong>")
1048
+ .replace(/\*([^*\n]+)\*/g, "<em>$1</em>")
1049
+ .replace(/~~([^~\n]+)~~/g, "<del>$1</del>");
1050
+
1051
+ return html.replace(/\u0000(\d+)\u0000/g, (_match, index) => placeholders[Number(index)] || "");
1052
+ }
1053
+
1054
+ function splitTableRow(line) {
1055
+ return String(line || "")
1056
+ .trim()
1057
+ .replace(/^\|/, "")
1058
+ .replace(/\|$/, "")
1059
+ .split("|")
1060
+ .map((cell) => cell.trim());
1061
+ }
1062
+
1063
+ function isMarkdownTableSeparator(line) {
1064
+ return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(String(line || ""));
1065
+ }
1066
+
1067
+ function renderMarkdownTable(headerLine, rows) {
1068
+ const headers = splitTableRow(headerLine);
1069
+ const bodyRows = rows.map(splitTableRow);
1070
+ return `
1071
+ <div class="markdown-table-wrap">
1072
+ <table>
1073
+ <thead>
1074
+ <tr>${headers.map((cell) => `<th>${renderInlineMarkdown(cell)}</th>`).join("")}</tr>
1075
+ </thead>
1076
+ <tbody>
1077
+ ${bodyRows
1078
+ .map((row) => `<tr>${headers.map((_header, index) => `<td>${renderInlineMarkdown(row[index] || "")}</td>`).join("")}</tr>`)
1079
+ .join("")}
1080
+ </tbody>
1081
+ </table>
1082
+ </div>
1083
+ `;
1084
+ }
1085
+
1086
+ function isMarkdownBlockStart(line, nextLine = "") {
1087
+ const trimmed = String(line || "").trim();
1088
+ return (
1089
+ /^```/.test(trimmed) ||
1090
+ /^#{1,6}\s+/.test(trimmed) ||
1091
+ /^[-*_]{3,}$/.test(trimmed) ||
1092
+ /^>\s?/.test(trimmed) ||
1093
+ /^[-*+]\s+/.test(trimmed) ||
1094
+ /^\d+\.\s+/.test(trimmed) ||
1095
+ (trimmed.includes("|") && isMarkdownTableSeparator(nextLine))
1096
+ );
1097
+ }
1098
+
1099
+ function renderMarkdown(value) {
1100
+ const lines = String(value || "").replace(/\r\n?/g, "\n").split("\n");
1101
+ const html = [];
1102
+ let paragraph = [];
1103
+ let listType = "";
1104
+ let listItems = [];
1105
+
1106
+ const flushParagraph = () => {
1107
+ if (!paragraph.length) return;
1108
+ html.push(`<p>${renderInlineMarkdown(paragraph.join(" ").trim())}</p>`);
1109
+ paragraph = [];
1110
+ };
1111
+
1112
+ const flushList = () => {
1113
+ if (!listItems.length) return;
1114
+ const tag = listType === "ol" ? "ol" : "ul";
1115
+ html.push(`<${tag}>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("")}</${tag}>`);
1116
+ listType = "";
1117
+ listItems = [];
1118
+ };
1119
+
1120
+ for (let index = 0; index < lines.length; index += 1) {
1121
+ const line = lines[index];
1122
+ const trimmed = line.trim();
1123
+
1124
+ if (!trimmed) {
1125
+ flushParagraph();
1126
+ flushList();
1127
+ continue;
1128
+ }
1129
+
1130
+ const fence = trimmed.match(/^```([A-Za-z0-9_-]+)?\s*$/);
1131
+ if (fence) {
1132
+ flushParagraph();
1133
+ flushList();
1134
+ const language = fence[1] || "";
1135
+ const code = [];
1136
+ index += 1;
1137
+ while (index < lines.length && !/^```\s*$/.test(lines[index].trim())) {
1138
+ code.push(lines[index]);
1139
+ index += 1;
1140
+ }
1141
+ html.push(
1142
+ `<pre class="markdown-code"><code data-language="${escapeHtml(language)}">${escapeHtml(code.join("\n"))}</code></pre>`
1143
+ );
1144
+ continue;
1145
+ }
1146
+
1147
+ const heading = trimmed.match(/^(#{1,6})\s+(.+?)\s*#*$/);
1148
+ if (heading) {
1149
+ flushParagraph();
1150
+ flushList();
1151
+ const level = heading[1].length;
1152
+ html.push(`<h${level}>${renderInlineMarkdown(heading[2])}</h${level}>`);
1153
+ continue;
1154
+ }
1155
+
1156
+ if (/^[-*_]{3,}$/.test(trimmed)) {
1157
+ flushParagraph();
1158
+ flushList();
1159
+ html.push("<hr>");
1160
+ continue;
1161
+ }
1162
+
1163
+ if (trimmed.includes("|") && isMarkdownTableSeparator(lines[index + 1])) {
1164
+ flushParagraph();
1165
+ flushList();
1166
+ const header = line;
1167
+ const rows = [];
1168
+ index += 2;
1169
+ while (index < lines.length && lines[index].trim().includes("|")) {
1170
+ rows.push(lines[index]);
1171
+ index += 1;
1172
+ }
1173
+ index -= 1;
1174
+ html.push(renderMarkdownTable(header, rows));
1175
+ continue;
1176
+ }
1177
+
1178
+ if (/^>\s?/.test(trimmed)) {
1179
+ flushParagraph();
1180
+ flushList();
1181
+ const quote = [];
1182
+ while (index < lines.length && /^>\s?/.test(lines[index].trim())) {
1183
+ quote.push(lines[index].trim().replace(/^>\s?/, ""));
1184
+ index += 1;
1185
+ }
1186
+ index -= 1;
1187
+ html.push(`<blockquote>${renderMarkdown(quote.join("\n"))}</blockquote>`);
1188
+ continue;
1189
+ }
1190
+
1191
+ const unordered = trimmed.match(/^[-*+]\s+(.+)$/);
1192
+ const ordered = trimmed.match(/^\d+\.\s+(.+)$/);
1193
+ if (unordered || ordered) {
1194
+ flushParagraph();
1195
+ const nextType = ordered ? "ol" : "ul";
1196
+ if (listType && listType !== nextType) flushList();
1197
+ listType = nextType;
1198
+ listItems.push((unordered || ordered)[1]);
1199
+ continue;
1200
+ }
1201
+
1202
+ if (listItems.length) flushList();
1203
+ paragraph.push(trimmed);
1204
+ if (index + 1 >= lines.length || isMarkdownBlockStart(lines[index + 1], lines[index + 2])) flushParagraph();
1205
+ }
1206
+
1207
+ flushParagraph();
1208
+ flushList();
1209
+ return html.join("");
1210
+ }
1211
+
1017
1212
  function renderChat(chatEntries) {
1018
1213
  lastChatEntries = chatEntries || [];
1019
1214
 
@@ -1026,7 +1221,10 @@ function renderChat(chatEntries) {
1026
1221
  .map((entry) => {
1027
1222
  const role = entry.role === "assistant" ? "assistant" : "user";
1028
1223
  const label = role === "assistant" ? t("assistantLabel") : t("youLabel");
1029
- const content = escapeHtml(entry.content).replace(/\n/g, "<br>");
1224
+ const content =
1225
+ role === "assistant"
1226
+ ? `<div class="markdown-body">${renderMarkdown(entry.content)}</div>`
1227
+ : escapeHtml(entry.content).replace(/\n/g, "<br>");
1030
1228
  return `
1031
1229
  <article class="chat-item ${role}">
1032
1230
  <div class="chat-meta">${label}${entry.at ? ` · ${new Date(entry.at).toLocaleString()}` : ""}</div>
@@ -1200,6 +1398,11 @@ function renderArtifactContent(content) {
1200
1398
  }
1201
1399
 
1202
1400
  const text = typeof content.text === "string" ? content.text : "";
1401
+ if (content.kind === "markdown" || /markdown/i.test(content.mime || "")) {
1402
+ artifactViewerBodyEl.innerHTML = `<div class="artifact-markdown markdown-body">${renderMarkdown(text)}</div>`;
1403
+ return;
1404
+ }
1405
+
1203
1406
  artifactViewerBodyEl.innerHTML = `
1204
1407
  <textarea class="artifact-editor" readonly spellcheck="false">${escapeHtml(text)}</textarea>
1205
1408
  `;
package/public/styles.css CHANGED
@@ -587,6 +587,139 @@ button.danger {
587
587
  overflow-wrap: anywhere;
588
588
  }
589
589
 
590
+ .markdown-body {
591
+ line-height: 1.58;
592
+ }
593
+
594
+ .markdown-body > :first-child {
595
+ margin-top: 0;
596
+ }
597
+
598
+ .markdown-body > :last-child {
599
+ margin-bottom: 0;
600
+ }
601
+
602
+ .markdown-body h1,
603
+ .markdown-body h2,
604
+ .markdown-body h3,
605
+ .markdown-body h4,
606
+ .markdown-body h5,
607
+ .markdown-body h6 {
608
+ margin: 0.92em 0 0.42em;
609
+ color: var(--ink);
610
+ line-height: 1.15;
611
+ }
612
+
613
+ .markdown-body h1 {
614
+ font-size: 1.38rem;
615
+ }
616
+
617
+ .markdown-body h2 {
618
+ font-size: 1.22rem;
619
+ }
620
+
621
+ .markdown-body h3 {
622
+ font-size: 1.08rem;
623
+ }
624
+
625
+ .markdown-body p,
626
+ .markdown-body ul,
627
+ .markdown-body ol,
628
+ .markdown-body blockquote,
629
+ .markdown-body pre,
630
+ .markdown-table-wrap {
631
+ margin: 0 0 0.82em;
632
+ }
633
+
634
+ .markdown-body ul,
635
+ .markdown-body ol {
636
+ padding-left: 1.35rem;
637
+ }
638
+
639
+ .markdown-body li + li {
640
+ margin-top: 0.28em;
641
+ }
642
+
643
+ .markdown-body a {
644
+ color: #0f766e;
645
+ font-weight: 800;
646
+ text-decoration-thickness: 0.08em;
647
+ text-underline-offset: 0.16em;
648
+ }
649
+
650
+ .markdown-body code {
651
+ padding: 0.12em 0.36em;
652
+ border: 1px solid rgba(15, 118, 110, 0.16);
653
+ border-radius: 7px;
654
+ background: rgba(15, 23, 42, 0.06);
655
+ color: #0f172a;
656
+ font-size: 0.92em;
657
+ }
658
+
659
+ .markdown-body pre {
660
+ max-width: 100%;
661
+ padding: 12px;
662
+ border: 1px solid rgba(15, 118, 110, 0.18);
663
+ border-radius: 14px;
664
+ background: #111827;
665
+ color: #f8fafc;
666
+ overflow: auto;
667
+ white-space: pre;
668
+ }
669
+
670
+ .markdown-body pre code {
671
+ padding: 0;
672
+ border: 0;
673
+ background: transparent;
674
+ color: inherit;
675
+ }
676
+
677
+ .markdown-body blockquote {
678
+ padding: 0.2rem 0 0.2rem 0.9rem;
679
+ border-left: 4px solid rgba(15, 118, 110, 0.32);
680
+ color: var(--muted);
681
+ }
682
+
683
+ .markdown-body hr {
684
+ height: 1px;
685
+ margin: 1rem 0;
686
+ border: 0;
687
+ background: var(--line);
688
+ }
689
+
690
+ .markdown-table-wrap {
691
+ max-width: 100%;
692
+ overflow: auto;
693
+ }
694
+
695
+ .markdown-body table {
696
+ width: 100%;
697
+ min-width: 420px;
698
+ border-collapse: collapse;
699
+ font-size: 0.92rem;
700
+ }
701
+
702
+ .markdown-body th,
703
+ .markdown-body td {
704
+ padding: 8px 10px;
705
+ border: 1px solid rgba(15, 118, 110, 0.16);
706
+ text-align: left;
707
+ vertical-align: top;
708
+ }
709
+
710
+ .markdown-body th {
711
+ background: rgba(15, 118, 110, 0.08);
712
+ }
713
+
714
+ .artifact-markdown {
715
+ min-height: 100%;
716
+ padding: 14px;
717
+ border: 1px solid rgba(15, 118, 110, 0.14);
718
+ border-radius: 16px;
719
+ background: rgba(255, 255, 255, 0.72);
720
+ overflow: auto;
721
+ }
722
+
590
723
  .chat-form {
591
724
  display: grid;
592
725
  gap: 8px;
@@ -60,7 +60,7 @@ function label(name, bgCode) {
60
60
  }
61
61
 
62
62
  function userPrompt() {
63
- return `\n${label("user>", ansi.userBg)} `;
63
+ return `\n${label("user>", ansi.userBg)} ${color("|", ansi.userBg)} `;
64
64
  }
65
65
 
66
66
  function commandCompleter(line = "") {
@@ -70,6 +70,21 @@ function commandCompleter(line = "") {
70
70
  return [hits.length > 0 ? hits : SLASH_COMMANDS, trimmed];
71
71
  }
72
72
 
73
+ function stripAnsi(value) {
74
+ return String(value || "").replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
75
+ }
76
+
77
+ function promptGutter() {
78
+ const visible = stripAnsi(userPrompt()).replace(/^\n/, "").length;
79
+ return " ".repeat(Math.max(visible - 2, 0)) + `${color("|", ansi.userBg)} `;
80
+ }
81
+
82
+ function commandSuggestions(line = "") {
83
+ const trimmed = String(line || "");
84
+ if (!trimmed.startsWith("/") || /\s/.test(trimmed)) return [];
85
+ return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
86
+ }
87
+
73
88
  function stripMarkdown(text) {
74
89
  const lines = String(text || "").split(/\r?\n/);
75
90
  let inFence = false;
@@ -79,16 +94,26 @@ function stripMarkdown(text) {
79
94
  let line = rawLine;
80
95
  if (/^\s*```/.test(line)) {
81
96
  inFence = !inFence;
82
- if (inFence) rendered.push(color("code", ansi.dim));
97
+ if (inFence) {
98
+ const language = line.replace(/^\s*```/, "").trim();
99
+ rendered.push(color(language ? `code ${language}` : "code", ansi.dim));
100
+ }
83
101
  continue;
84
102
  }
85
103
 
86
104
  if (!inFence) {
87
105
  if (/^\s*[-*_]{3,}\s*$/.test(line)) {
88
- rendered.push("");
106
+ rendered.push(color("-".repeat(42), ansi.dim));
107
+ continue;
108
+ }
109
+ const heading = line.match(/^\s{0,3}(#{1,6})\s+(.+)$/);
110
+ if (heading) {
111
+ rendered.push(color(heading[2].replace(/\s+#*$/, ""), ansi.bold, ansi.cyan));
112
+ continue;
113
+ }
114
+ if (/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line)) {
89
115
  continue;
90
116
  }
91
- line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
92
117
  line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
93
118
  line = line.replace(/\*\*([^*]+)\*\*/g, (_, value) => color(value, ansi.bold));
94
119
  line = line.replace(/__([^_]+)__/g, (_, value) => color(value, ansi.bold));
@@ -96,7 +121,9 @@ function stripMarkdown(text) {
96
121
  line = line.replace(/(^|[^\w])_([^_\n]+)_/g, "$1$2");
97
122
  line = line.replace(/`([^`]+)`/g, (_, value) => color(value, ansi.yellow));
98
123
  line = line.replace(/^(\s*)[-*+]\s+/, "$1- ");
99
- line = line.replace(/^\s*>\s?/, " ");
124
+ line = line.replace(/^\s*>\s?(.+)$/, (_, value) => color(`| ${value}`, ansi.dim));
125
+ } else {
126
+ line = color(` ${line}`, ansi.yellow);
100
127
  }
101
128
 
102
129
  rendered.push(line);
@@ -105,10 +132,15 @@ function stripMarkdown(text) {
105
132
  return rendered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
106
133
  }
107
134
 
108
- function printWrapped(prefix, text) {
135
+ function rolePrefix(name, bgCode) {
136
+ return `${label(name, bgCode)} ${color("|", bgCode)} `;
137
+ }
138
+
139
+ function printWrapped(prefix, text, { stripCode = "" } = {}) {
109
140
  const rendered = stripMarkdown(text);
110
141
  const lines = rendered.split("\n");
111
- const gutter = " ".repeat(useColor ? 9 : prefix.length);
142
+ const visible = stripAnsi(prefix).length;
143
+ const gutter = `${" ".repeat(Math.max(visible - 2, 0))}${stripCode ? color("|", stripCode) : "|"} `;
112
144
  console.log(`${prefix}${lines[0] || ""}`);
113
145
  for (const line of lines.slice(1)) {
114
146
  console.log(`${gutter}${line}`);
@@ -116,7 +148,7 @@ function printWrapped(prefix, text) {
116
148
  }
117
149
 
118
150
  function printAgentMessage(text) {
119
- printWrapped(`${label("aginti>", ansi.agentBg)} `, text);
151
+ printWrapped(rolePrefix("aginti>", ansi.agentBg), text, { stripCode: ansi.agentBg });
120
152
  }
121
153
 
122
154
  function printSystemLine(text) {
@@ -197,6 +229,113 @@ function printHelp() {
197
229
  );
198
230
  }
199
231
 
232
+ function renderPromptBuffer(buffer, previousLineCount = 0) {
233
+ for (let index = 0; index < previousLineCount; index += 1) {
234
+ output.write(`\r${ansi.clearLine}`);
235
+ if (index < previousLineCount - 1) output.write("\x1b[1A");
236
+ }
237
+
238
+ const lines = String(buffer || "").split("\n");
239
+ const suggestions = commandSuggestions(lines[0] || "");
240
+ const rendered = [];
241
+ rendered.push(`${userPrompt().replace(/^\n/, "")}${lines[0] || ""}`);
242
+ for (const line of lines.slice(1)) {
243
+ rendered.push(`${promptGutter()}${line}`);
244
+ }
245
+ if (suggestions.length > 0) {
246
+ rendered.push(`${promptGutter()}${color(`suggest: ${suggestions.join(" ")}`, ansi.dim)}`);
247
+ }
248
+ output.write(rendered.join("\n"));
249
+ return rendered.length;
250
+ }
251
+
252
+ function createAbortError(message = "Aborted with Ctrl+C") {
253
+ const error = new Error(message);
254
+ error.code = "ABORT_ERR";
255
+ error.name = "AbortError";
256
+ return error;
257
+ }
258
+
259
+ function readTtyPrompt() {
260
+ return new Promise((resolve, reject) => {
261
+ emitKeypressEvents(input);
262
+ const wasRaw = Boolean(input.isRaw);
263
+ let buffer = "";
264
+ let renderedLines = 0;
265
+
266
+ const cleanup = () => {
267
+ input.off("keypress", handler);
268
+ if (typeof input.setRawMode === "function") input.setRawMode(wasRaw);
269
+ input.pause();
270
+ output.write(ansi.cursorShow);
271
+ };
272
+
273
+ const redraw = () => {
274
+ renderedLines = renderPromptBuffer(buffer, renderedLines);
275
+ };
276
+
277
+ const submit = () => {
278
+ cleanup();
279
+ output.write("\n");
280
+ resolve(buffer);
281
+ };
282
+
283
+ const handler = (str = "", key = {}) => {
284
+ if (key.ctrl && key.name === "c") {
285
+ cleanup();
286
+ output.write("\n");
287
+ reject(createAbortError());
288
+ return;
289
+ }
290
+ if ((key.ctrl && key.name === "j") || (key.sequence === "\n" && key.name !== "return" && key.name !== "enter")) {
291
+ buffer += "\n";
292
+ redraw();
293
+ return;
294
+ }
295
+ if (key.name === "return" || key.name === "enter" || key.sequence === "\r" || str === "\r") {
296
+ submit();
297
+ return;
298
+ }
299
+ if (key.name === "backspace") {
300
+ buffer = buffer.slice(0, -1);
301
+ redraw();
302
+ return;
303
+ }
304
+ if (key.name === "tab") {
305
+ const suggestions = commandSuggestions(buffer.split("\n")[0] || "");
306
+ if (suggestions.length === 1) {
307
+ buffer = suggestions[0];
308
+ }
309
+ redraw();
310
+ return;
311
+ }
312
+ if (key.name === "escape") {
313
+ buffer = "";
314
+ redraw();
315
+ return;
316
+ }
317
+ if (key.ctrl || key.meta) return;
318
+ if (str && !key.sequence?.startsWith("\x1b")) {
319
+ buffer += str;
320
+ redraw();
321
+ }
322
+ };
323
+
324
+ input.resume();
325
+ input.setRawMode(true);
326
+ output.write(ansi.cursorHide);
327
+ input.on("keypress", handler);
328
+ redraw();
329
+ });
330
+ }
331
+
332
+ async function readPromptAnswer(rl) {
333
+ if (input.isTTY && output.isTTY && typeof input.setRawMode === "function") {
334
+ return readTtyPrompt();
335
+ }
336
+ return rl.question(userPrompt());
337
+ }
338
+
200
339
  function printStatus(state) {
201
340
  printSystemLine(`project=${process.cwd()}`);
202
341
  printSystemLine(`cwd=${state.commandCwd || process.cwd()}`);
@@ -237,6 +376,7 @@ function attachRunInterrupts(controller) {
237
376
 
238
377
  emitKeypressEvents(input);
239
378
  const wasRaw = Boolean(input.isRaw);
379
+ input.resume();
240
380
  input.setRawMode(true);
241
381
  const handler = (_str, key = {}) => {
242
382
  const isEscape = key.name === "escape";
@@ -564,12 +704,15 @@ async function runPrompt(prompt, state, packageDir) {
564
704
 
565
705
  export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
566
706
  const state = createState(args);
567
- const rl = readline.createInterface({
568
- input,
569
- output,
570
- terminal: Boolean(input.isTTY && output.isTTY),
571
- completer: commandCompleter,
572
- });
707
+ const rl =
708
+ input.isTTY && output.isTTY
709
+ ? null
710
+ : readline.createInterface({
711
+ input,
712
+ output,
713
+ terminal: false,
714
+ completer: commandCompleter,
715
+ });
573
716
 
574
717
  await renderLaunchHeader(packageVersion);
575
718
  printSystemLine(`Project: ${process.cwd()}`);
@@ -581,7 +724,7 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
581
724
  while (true) {
582
725
  let answer = "";
583
726
  try {
584
- answer = await rl.question(userPrompt());
727
+ answer = await readPromptAnswer(rl);
585
728
  } catch (error) {
586
729
  if (error?.code === "ERR_USE_AFTER_CLOSE") break;
587
730
  if (isAbortError(error)) {
@@ -609,6 +752,6 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
609
752
  }
610
753
  }
611
754
  } finally {
612
- rl.close();
755
+ rl?.close();
613
756
  }
614
757
  }