@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 +1 -1
- package/package.json +1 -1
- package/scripts/smoke-cli-chat.js +19 -1
- package/src/interactive-cli.js +124 -13
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
|
@@ -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
|
package/src/interactive-cli.js
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
console.log(
|
|
205
|
-
console.log(color(
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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(
|
|
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") ||
|
|
410
|
+
if ((key.ctrl && key.name === "j") || key.sequence === "\n") {
|
|
300
411
|
buffer += "\n";
|
|
301
412
|
redraw();
|
|
302
413
|
return;
|