@lazyingart/agintiflow 0.12.0 → 0.12.1

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. 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, 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.
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.0",
3
+ "version": "0.12.1",
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,6 +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
8
 
8
9
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
10
  const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-"));
@@ -52,6 +53,23 @@ function runCli(args, inputText) {
52
53
  }
53
54
 
54
55
  try {
56
+ const renderedMarkdown = stripMarkdown(
57
+ [
58
+ "**Docker status**",
59
+ "",
60
+ "| Check | Result |",
61
+ "| --- | --- |",
62
+ "| `/.dockerenv` | **Present** |",
63
+ "| Hostname | `abc123` |",
64
+ ].join("\n")
65
+ );
66
+ if (renderedMarkdown.includes("**") || renderedMarkdown.includes("| --- |")) {
67
+ throw new Error("terminal markdown renderer left raw markdown syntax");
68
+ }
69
+ if (!renderedMarkdown.includes("Check") || !renderedMarkdown.includes("Present")) {
70
+ throw new Error("terminal markdown renderer dropped table content");
71
+ }
72
+
55
73
  const result = await runChat("Create notes/interactive.md with a short CLI chat smoke message\n/exit\n");
56
74
  const written = await fs.readFile(path.join(tempRoot, "notes/interactive.md"), "utf8");
57
75
  if (!written.includes("Created by AgInTiFlow mock mode.")) {
@@ -74,7 +92,7 @@ try {
74
92
  {
75
93
  ok: true,
76
94
  projectRoot: tempRoot,
77
- checks: ["interactive-chat", "mock-file-write", "run-status", "resume-latest"],
95
+ checks: ["markdown-render", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
78
96
  },
79
97
  null,
80
98
  2
@@ -18,6 +18,9 @@ const ansi = {
18
18
  green: "\x1b[32m",
19
19
  yellow: "\x1b[33m",
20
20
  red: "\x1b[31m",
21
+ blue: "\x1b[34m",
22
+ magenta: "\x1b[35m",
23
+ faint: "\x1b[2m",
21
24
  clearLine: "\x1b[2K",
22
25
  cursorHide: "\x1b[?25l",
23
26
  cursorShow: "\x1b[?25h",
@@ -64,6 +67,26 @@ function label(name, bgCode) {
64
67
  return color(` ${name} `, bgCode, ansi.bold);
65
68
  }
66
69
 
70
+ function terminalWidth() {
71
+ return Math.max(Number(output.columns) || 80, 40);
72
+ }
73
+
74
+ function visualLength(value) {
75
+ return stripAnsi(value).length;
76
+ }
77
+
78
+ function padVisible(value, width) {
79
+ const padding = Math.max(width - visualLength(value), 0);
80
+ return `${value}${" ".repeat(padding)}`;
81
+ }
82
+
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}`);
87
+ return `${bgCode}${padded}${ansi.reset}`;
88
+ }
89
+
67
90
  function userPrompt() {
68
91
  return `\n${label("user>", ansi.userBg)} ${color("|", ansi.userBg)} `;
69
92
  }
@@ -90,12 +113,13 @@ function commandSuggestions(line = "") {
90
113
  return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
91
114
  }
92
115
 
93
- function stripMarkdown(text) {
116
+ export function stripMarkdown(text) {
94
117
  const lines = String(text || "").split(/\r?\n/);
95
118
  let inFence = false;
96
119
  const rendered = [];
97
120
 
98
- for (const rawLine of lines) {
121
+ for (let index = 0; index < lines.length; index += 1) {
122
+ const rawLine = lines[index];
99
123
  let line = rawLine;
100
124
  if (/^\s*```/.test(line)) {
101
125
  inFence = !inFence;
@@ -119,6 +143,12 @@ function stripMarkdown(text) {
119
143
  if (/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line)) {
120
144
  continue;
121
145
  }
146
+ const table = parseMarkdownTable(lines, index);
147
+ if (table) {
148
+ rendered.push(...renderMarkdownTable(table));
149
+ index += table.rawLineCount - 1;
150
+ continue;
151
+ }
122
152
  line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
123
153
  line = line.replace(/\*\*([^*]+)\*\*/g, (_, value) => color(value, ansi.bold));
124
154
  line = line.replace(/__([^_]+)__/g, (_, value) => color(value, ansi.bold));
@@ -137,6 +167,75 @@ function stripMarkdown(text) {
137
167
  return rendered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
138
168
  }
139
169
 
170
+ function splitMarkdownTableRow(line = "") {
171
+ const trimmed = String(line || "").trim();
172
+ if (!trimmed.includes("|")) return null;
173
+ const normalized = trimmed.replace(/^\|/, "").replace(/\|$/, "");
174
+ const cells = normalized.split("|").map((cell) => cell.trim());
175
+ return cells.length >= 2 ? cells : null;
176
+ }
177
+
178
+ function isMarkdownTableSeparator(line = "") {
179
+ const cells = splitMarkdownTableRow(line);
180
+ return Boolean(cells?.length) && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
181
+ }
182
+
183
+ function parseMarkdownTable(lines, startIndex) {
184
+ const header = splitMarkdownTableRow(lines[startIndex]);
185
+ if (!header || !isMarkdownTableSeparator(lines[startIndex + 1] || "")) return null;
186
+ const rows = [];
187
+ let index = startIndex + 2;
188
+ while (index < lines.length) {
189
+ const row = splitMarkdownTableRow(lines[index]);
190
+ if (!row) break;
191
+ rows.push(row);
192
+ index += 1;
193
+ }
194
+ return {
195
+ header,
196
+ rows,
197
+ rawLineCount: Math.max(index - startIndex, 2),
198
+ };
199
+ }
200
+
201
+ function renderMarkdownTable(table) {
202
+ const allRows = [table.header, ...table.rows];
203
+ const columnCount = Math.max(...allRows.map((row) => row.length));
204
+ const widths = Array.from({ length: columnCount }, (_unused, column) =>
205
+ Math.min(
206
+ Math.max(
207
+ ...allRows.map((row) => visualLength(stripMarkdownInline(row[column] || ""))),
208
+ 3
209
+ ),
210
+ 36
211
+ )
212
+ );
213
+ const formatRow = (row, header = false) =>
214
+ widths
215
+ .map((width, column) => {
216
+ const value = stripMarkdownInline(row[column] || "");
217
+ return padVisible(value, width);
218
+ })
219
+ .join(color(" │ ", ansi.dim));
220
+ const separator = widths.map((width) => "─".repeat(width)).join(color("──┼──", ansi.dim));
221
+ return [
222
+ color(formatRow(table.header, true), ansi.bold, ansi.cyan),
223
+ color(separator, ansi.dim),
224
+ ...table.rows.map((row) => formatRow(row)),
225
+ ];
226
+ }
227
+
228
+ function stripMarkdownInline(value = "") {
229
+ return String(value)
230
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)")
231
+ .replace(/\*\*([^*]+)\*\*/g, (_, text) => color(text, ansi.bold))
232
+ .replace(/__([^_]+)__/g, (_, text) => color(text, ansi.bold))
233
+ .replace(/`([^`]+)`/g, (_, text) => color(text, ansi.yellow))
234
+ .replace(/~~([^~]+)~~/g, "$1")
235
+ .replace(/\*([^*]+)\*/g, "$1")
236
+ .replace(/_([^_]+)_/g, "$1");
237
+ }
238
+
140
239
  function rolePrefix(name, bgCode) {
141
240
  return `${label(name, bgCode)} ${color("|", bgCode)} `;
142
241
  }
@@ -183,7 +282,10 @@ async function renderLaunchHeader(packageVersion = "") {
183
282
  const title = "AgInTi Flow";
184
283
  const subtitle = "web-first agent workspace";
185
284
  const version = packageVersion ? `v${packageVersion}` : "";
186
- const line = "+--------------------------------------------------+";
285
+ const width = Math.min(Math.max(terminalWidth() - 2, 58), 82);
286
+ const top = `╭${"─".repeat(width)}╮`;
287
+ const mid = `├${"─".repeat(width)}┤`;
288
+ const bottom = `╰${"─".repeat(width)}╯`;
187
289
 
188
290
  if (!useColor || process.env.AGINTIFLOW_NO_ANIMATION === "1") {
189
291
  console.log(` AgInTiFlow ${packageVersion || ""}`.trim());
@@ -198,11 +300,15 @@ async function renderLaunchHeader(packageVersion = "") {
198
300
  output.write(`\r${ansi.clearLine}`);
199
301
  output.write(ansi.cursorShow);
200
302
 
201
- console.log(color(line, "\x1b[38;5;45m"));
202
- console.log(`${color("|", "\x1b[38;5;45m")} ${shimmerText(title, 2)} ${color(version.padStart(36 - title.length), ansi.dim)} ${color("|", "\x1b[38;5;45m")}`);
203
- console.log(`${color("|", "\x1b[38;5;45m")} ${color(subtitle.padEnd(48), ansi.dim)} ${color("|", "\x1b[38;5;45m")}`);
204
- console.log(`${color("|", "\x1b[38;5;45m")} ${color("browser + shell + files + docker + canvas".padEnd(48), ansi.cyan)} ${color("|", "\x1b[38;5;45m")}`);
205
- console.log(color(line, "\x1b[38;5;45m"));
303
+ const border = "\x1b[38;5;45m";
304
+ const titleLine = `${shimmerText(title, 2)} ${color(version, ansi.dim)}`;
305
+ const tagline = "browser + shell + files + docker + web search + scouts";
306
+ console.log(color(top, border));
307
+ console.log(`${color("│", border)} ${padVisible(titleLine, width - 2)} ${color("", border)}`);
308
+ console.log(`${color("│", border)} ${color(padVisible(subtitle, width - 2), ansi.dim)} ${color("│", border)}`);
309
+ console.log(color(mid, border));
310
+ console.log(`${color("│", border)} ${color(padVisible(tagline, width - 2), ansi.cyan)} ${color("│", border)}`);
311
+ console.log(color(bottom, border));
206
312
  }
207
313
 
208
314
  function printHelp() {
@@ -247,12 +353,17 @@ function renderPromptBuffer(buffer, previousLineCount = 0) {
247
353
  const lines = String(buffer || "").split("\n");
248
354
  const suggestions = commandSuggestions(lines[0] || "");
249
355
  const rendered = [];
250
- rendered.push(`${userPrompt().replace(/^\n/, "")}${lines[0] || ""}`);
251
- for (const line of lines.slice(1)) {
252
- rendered.push(`${promptGutter()}${line}`);
356
+ const firstPrefix = " user ";
357
+ 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));
253
364
  }
254
365
  if (suggestions.length > 0) {
255
- rendered.push(`${promptGutter()}${color(`suggest: ${suggestions.join(" ")}`, ansi.dim)}`);
366
+ rendered.push(panelLine(` hint ${color(suggestions.join(" "), ansi.dim)}`, ansi.systemBg));
256
367
  }
257
368
  output.write(rendered.join("\n"));
258
369
  return rendered.length;
@@ -296,7 +407,7 @@ function readTtyPrompt() {
296
407
  reject(createAbortError());
297
408
  return;
298
409
  }
299
- if ((key.ctrl && key.name === "j") || (key.sequence === "\n" && key.name !== "return" && key.name !== "enter")) {
410
+ if ((key.ctrl && key.name === "j") || key.sequence === "\n") {
300
411
  buffer += "\n";
301
412
  redraw();
302
413
  return;