@thegitai/cli 1.0.0-beta.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/LICENSE +20 -0
- package/README.md +30 -0
- package/dist/bin/ai.js +438 -0
- package/dist/parsers/tree-sitter-c-sharp.wasm +0 -0
- package/dist/parsers/tree-sitter-c.wasm +0 -0
- package/dist/parsers/tree-sitter-cpp.wasm +0 -0
- package/dist/parsers/tree-sitter-css.wasm +0 -0
- package/dist/parsers/tree-sitter-go.wasm +0 -0
- package/dist/parsers/tree-sitter-html.wasm +0 -0
- package/dist/parsers/tree-sitter-java.wasm +0 -0
- package/dist/parsers/tree-sitter-javascript.wasm +0 -0
- package/dist/parsers/tree-sitter-objc.wasm +0 -0
- package/dist/parsers/tree-sitter-php.wasm +0 -0
- package/dist/parsers/tree-sitter-python.wasm +0 -0
- package/dist/parsers/tree-sitter-ruby.wasm +0 -0
- package/dist/parsers/tree-sitter-rust.wasm +0 -0
- package/dist/parsers/tree-sitter-tsx.wasm +0 -0
- package/dist/parsers/tree-sitter-typescript.wasm +0 -0
- package/dist/src/agent-mode.js +142 -0
- package/dist/src/api/auth.js +81 -0
- package/dist/src/api/browser-login.js +184 -0
- package/dist/src/api/chat.js +346 -0
- package/dist/src/api/contracts.js +1 -0
- package/dist/src/api/http.js +44 -0
- package/dist/src/api/index.js +11 -0
- package/dist/src/api/models.js +110 -0
- package/dist/src/api/sessions.js +72 -0
- package/dist/src/artifact-policy.js +207 -0
- package/dist/src/client-state.js +14 -0
- package/dist/src/core/clipboard.js +208 -0
- package/dist/src/core/open-url.js +32 -0
- package/dist/src/edit-journal.js +133 -0
- package/dist/src/executor.js +924 -0
- package/dist/src/extractors/cpp.js +18 -0
- package/dist/src/extractors/csharp.js +16 -0
- package/dist/src/extractors/css.js +12 -0
- package/dist/src/extractors/go.js +27 -0
- package/dist/src/extractors/index.js +52 -0
- package/dist/src/extractors/java.js +14 -0
- package/dist/src/extractors/javascript.js +33 -0
- package/dist/src/extractors/objc.js +14 -0
- package/dist/src/extractors/php.js +20 -0
- package/dist/src/extractors/python.js +11 -0
- package/dist/src/extractors/ruby.js +13 -0
- package/dist/src/extractors/rust.js +17 -0
- package/dist/src/extractors/utils.js +58 -0
- package/dist/src/help-text.js +125 -0
- package/dist/src/markdown-renderer.js +112 -0
- package/dist/src/patcher.js +279 -0
- package/dist/src/project-index.js +221 -0
- package/dist/src/repo-map-languages.js +100 -0
- package/dist/src/runtime-mode.js +35 -0
- package/dist/src/scanner.js +362 -0
- package/dist/src/secret-preview.js +137 -0
- package/dist/src/session-exit.js +17 -0
- package/dist/src/session-safety.js +1012 -0
- package/dist/src/session-store.js +266 -0
- package/dist/src/session.js +93 -0
- package/dist/src/tool-executor.js +188 -0
- package/dist/src/tools/code-intel.js +472 -0
- package/dist/src/tools/delete-file.js +27 -0
- package/dist/src/tools/exec-utils.js +17 -0
- package/dist/src/tools/find-symbol.js +70 -0
- package/dist/src/tools/get-diagnostics.js +22 -0
- package/dist/src/tools/grep-code.js +331 -0
- package/dist/src/tools/hover-symbol.js +95 -0
- package/dist/src/tools/index.js +73 -0
- package/dist/src/tools/list-checkpoints.js +11 -0
- package/dist/src/tools/list-directories.js +16 -0
- package/dist/src/tools/list-files.js +13 -0
- package/dist/src/tools/list-session-edits.js +9 -0
- package/dist/src/tools/list-symbols.js +55 -0
- package/dist/src/tools/patch-file.js +88 -0
- package/dist/src/tools/path-listing.js +83 -0
- package/dist/src/tools/read-document.js +111 -0
- package/dist/src/tools/read-file.js +109 -0
- package/dist/src/tools/restore-checkpoint.js +100 -0
- package/dist/src/tools/ripgrep.js +29 -0
- package/dist/src/tools/run-command.js +94 -0
- package/dist/src/tools/run-node-script.js +210 -0
- package/dist/src/tools/search-code.js +37 -0
- package/dist/src/tools/shell-diagnostics.js +707 -0
- package/dist/src/tools/signature-help.js +118 -0
- package/dist/src/tools/str-replace.js +193 -0
- package/dist/src/tools/types.js +1 -0
- package/dist/src/tools/undo-edit.js +202 -0
- package/dist/src/tools/write-file.js +59 -0
- package/dist/src/tree-sitter-runtime.js +135 -0
- package/dist/src/types.js +1 -0
- package/dist/src/ui/paste-collapse.js +22 -0
- package/dist/src/ui/prompt-history-store.js +96 -0
- package/dist/src/ui/repl.js +2238 -0
- package/dist/src/ui/tui/bridge.js +175 -0
- package/dist/src/ui/tui/build-frame.js +718 -0
- package/dist/src/ui/tui/markdown-render.js +455 -0
- package/dist/src/ui/tui/shell-input.js +488 -0
- package/dist/src/ui/tui/text.js +30 -0
- package/dist/src/ui/tui/types.js +1 -0
- package/dist/src/usage.js +47 -0
- package/dist/src/utils.js +38 -0
- package/package.json +38 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { line, plainLine, span, wrapText } from './text.js';
|
|
2
|
+
const TABLE_BORDER_WIDTH = 2;
|
|
3
|
+
const TABLE_CELL_OVERHEAD_WIDTH = 3;
|
|
4
|
+
function parseInlineSegments(text) {
|
|
5
|
+
const source = String(text ?? '');
|
|
6
|
+
const segments = [];
|
|
7
|
+
let cursor = 0;
|
|
8
|
+
while (cursor < source.length) {
|
|
9
|
+
if (source[cursor] === '[' && source[cursor - 1] !== '!') {
|
|
10
|
+
const labelEnd = source.indexOf('](', cursor + 1);
|
|
11
|
+
const urlEnd = labelEnd === -1 ? -1 : source.indexOf(')', labelEnd + 2);
|
|
12
|
+
if (labelEnd > cursor + 1 && urlEnd > labelEnd + 2) {
|
|
13
|
+
const url = normalizeLinkUrl(source.slice(labelEnd + 2, urlEnd));
|
|
14
|
+
if (url) {
|
|
15
|
+
const label = source.slice(cursor + 1, labelEnd);
|
|
16
|
+
segments.push({ kind: 'link', text: label || url, url });
|
|
17
|
+
cursor = urlEnd + 1;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (source.startsWith('**', cursor)) {
|
|
23
|
+
const end = source.indexOf('**', cursor + 2);
|
|
24
|
+
if (end > cursor + 2) {
|
|
25
|
+
segments.push({ kind: 'bold', text: source.slice(cursor + 2, end) });
|
|
26
|
+
cursor = end + 2;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (source[cursor] === '`') {
|
|
31
|
+
const end = source.indexOf('`', cursor + 1);
|
|
32
|
+
if (end > cursor + 1) {
|
|
33
|
+
segments.push({ kind: 'code', text: source.slice(cursor + 1, end) });
|
|
34
|
+
cursor = end + 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let nextCursor = source.length;
|
|
39
|
+
const nextBold = source.indexOf('**', cursor);
|
|
40
|
+
const nextCode = source.indexOf('`', cursor);
|
|
41
|
+
const nextLink = source.indexOf('[', cursor);
|
|
42
|
+
if (nextBold !== -1)
|
|
43
|
+
nextCursor = Math.min(nextCursor, nextBold);
|
|
44
|
+
if (nextCode !== -1)
|
|
45
|
+
nextCursor = Math.min(nextCursor, nextCode);
|
|
46
|
+
if (nextLink !== -1)
|
|
47
|
+
nextCursor = Math.min(nextCursor, nextLink);
|
|
48
|
+
if (nextCursor === cursor)
|
|
49
|
+
nextCursor += 1;
|
|
50
|
+
segments.push({ kind: 'text', text: source.slice(cursor, nextCursor) });
|
|
51
|
+
cursor = nextCursor;
|
|
52
|
+
}
|
|
53
|
+
return segments.filter((segment) => segment.text.length > 0);
|
|
54
|
+
}
|
|
55
|
+
function normalizeLinkUrl(value) {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = new URL(value);
|
|
58
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return parsed.href;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function isEscapedMarkdownPipe(source, pipeIndex) {
|
|
68
|
+
let slashCount = 0;
|
|
69
|
+
for (let index = pipeIndex - 1; index >= 0 && source[index] === '\\'; index--) {
|
|
70
|
+
slashCount++;
|
|
71
|
+
}
|
|
72
|
+
return slashCount % 2 === 1;
|
|
73
|
+
}
|
|
74
|
+
function splitMarkdownTableRow(line) {
|
|
75
|
+
const trimmed = String(line ?? '').trim();
|
|
76
|
+
const withoutLeadingPipe = trimmed.startsWith('|') ? trimmed.slice(1) : trimmed;
|
|
77
|
+
const trailingPipeIndex = withoutLeadingPipe.length - 1;
|
|
78
|
+
const hasTrailingBoundaryPipe = trailingPipeIndex >= 0 &&
|
|
79
|
+
withoutLeadingPipe[trailingPipeIndex] === '|' &&
|
|
80
|
+
!isEscapedMarkdownPipe(withoutLeadingPipe, trailingPipeIndex);
|
|
81
|
+
const withoutBoundaryPipes = hasTrailingBoundaryPipe
|
|
82
|
+
? withoutLeadingPipe.slice(0, -1)
|
|
83
|
+
: withoutLeadingPipe;
|
|
84
|
+
const cells = [];
|
|
85
|
+
let cell = '';
|
|
86
|
+
for (let index = 0; index < withoutBoundaryPipes.length; index++) {
|
|
87
|
+
const char = withoutBoundaryPipes[index] ?? '';
|
|
88
|
+
if (char === '|' && !isEscapedMarkdownPipe(withoutBoundaryPipes, index)) {
|
|
89
|
+
cells.push(cell.trim().replace(/\\\|/g, '|'));
|
|
90
|
+
cell = '';
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
cell += char;
|
|
94
|
+
}
|
|
95
|
+
cells.push(cell.trim().replace(/\\\|/g, '|'));
|
|
96
|
+
return cells;
|
|
97
|
+
}
|
|
98
|
+
function isMarkdownTableRow(line) {
|
|
99
|
+
const cells = splitMarkdownTableRow(line);
|
|
100
|
+
return cells.length >= 2 && String(line ?? '').includes('|');
|
|
101
|
+
}
|
|
102
|
+
function isMarkdownTableSeparator(line) {
|
|
103
|
+
const cells = splitMarkdownTableRow(line);
|
|
104
|
+
return cells.length >= 2 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
105
|
+
}
|
|
106
|
+
function stripInlineFormattingForWidth(text) {
|
|
107
|
+
return String(text ?? '')
|
|
108
|
+
.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, '$1')
|
|
109
|
+
.replace(/`([^`]*)`/g, '$1')
|
|
110
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1');
|
|
111
|
+
}
|
|
112
|
+
function fitTableColumnWidths(columnWidths, maxWidth) {
|
|
113
|
+
const widths = columnWidths.map((width) => Math.max(3, Math.floor(width)));
|
|
114
|
+
if (widths.length === 0)
|
|
115
|
+
return widths;
|
|
116
|
+
const overhead = TABLE_BORDER_WIDTH + widths.length * TABLE_CELL_OVERHEAD_WIDTH;
|
|
117
|
+
const budget = Math.max(widths.length * 3, maxWidth - overhead);
|
|
118
|
+
const total = widths.reduce((sum, width) => sum + width, 0);
|
|
119
|
+
if (total <= budget)
|
|
120
|
+
return widths;
|
|
121
|
+
let remaining = budget;
|
|
122
|
+
const scaled = widths.map((width) => {
|
|
123
|
+
const next = Math.max(3, Math.floor((width / total) * budget));
|
|
124
|
+
remaining -= next;
|
|
125
|
+
return next;
|
|
126
|
+
});
|
|
127
|
+
while (remaining > 0) {
|
|
128
|
+
let targetIndex = 0;
|
|
129
|
+
for (let index = 1; index < widths.length; index++) {
|
|
130
|
+
if (widths[index] - scaled[index] > widths[targetIndex] - scaled[targetIndex]) {
|
|
131
|
+
targetIndex = index;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
scaled[targetIndex]++;
|
|
135
|
+
remaining--;
|
|
136
|
+
}
|
|
137
|
+
while (remaining < 0) {
|
|
138
|
+
let targetIndex = -1;
|
|
139
|
+
for (let index = 0; index < scaled.length; index++) {
|
|
140
|
+
if (scaled[index] <= 3)
|
|
141
|
+
continue;
|
|
142
|
+
if (targetIndex === -1 || scaled[index] > scaled[targetIndex]) {
|
|
143
|
+
targetIndex = index;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (targetIndex === -1)
|
|
147
|
+
break;
|
|
148
|
+
scaled[targetIndex]--;
|
|
149
|
+
remaining++;
|
|
150
|
+
}
|
|
151
|
+
return scaled;
|
|
152
|
+
}
|
|
153
|
+
function normalizeMarkdownTableCells(cells, columnCount) {
|
|
154
|
+
return Array.from({ length: columnCount }, (_, index) => cells[index] ?? '');
|
|
155
|
+
}
|
|
156
|
+
function parseMarkdownTableBlock(lines, startIndex) {
|
|
157
|
+
const headerLine = lines[startIndex] ?? '';
|
|
158
|
+
const separatorLine = lines[startIndex + 1] ?? '';
|
|
159
|
+
if (!isMarkdownTableRow(headerLine) || !isMarkdownTableSeparator(separatorLine)) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const headers = splitMarkdownTableRow(headerLine);
|
|
163
|
+
const separators = splitMarkdownTableRow(separatorLine);
|
|
164
|
+
if (headers.length !== separators.length)
|
|
165
|
+
return null;
|
|
166
|
+
const columnCount = Math.max(headers.length, separators.length);
|
|
167
|
+
const rows = [];
|
|
168
|
+
let nextIndex = startIndex + 2;
|
|
169
|
+
while (nextIndex < lines.length && isMarkdownTableRow(lines[nextIndex] ?? '')) {
|
|
170
|
+
const cells = splitMarkdownTableRow(lines[nextIndex] ?? '');
|
|
171
|
+
if (cells.length !== columnCount)
|
|
172
|
+
break;
|
|
173
|
+
rows.push(normalizeMarkdownTableCells(cells, columnCount));
|
|
174
|
+
nextIndex++;
|
|
175
|
+
}
|
|
176
|
+
const normalizedHeaders = normalizeMarkdownTableCells(headers, columnCount);
|
|
177
|
+
const columnWidths = normalizedHeaders.map((header, columnIndex) => {
|
|
178
|
+
const values = [header, ...rows.map((row) => row[columnIndex] ?? '')];
|
|
179
|
+
return Math.max(3, ...values.map((value) => stripInlineFormattingForWidth(value).length));
|
|
180
|
+
});
|
|
181
|
+
return {
|
|
182
|
+
nextIndex,
|
|
183
|
+
table: { columnWidths, headers: normalizedHeaders, rows },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function splitFormattedLines(body) {
|
|
187
|
+
const lines = String(body ?? '')
|
|
188
|
+
.replace(/\r\n/g, '\n')
|
|
189
|
+
.replace(/\r/g, '\n')
|
|
190
|
+
.split('\n');
|
|
191
|
+
const formatted = [];
|
|
192
|
+
let inCodeBlock = false;
|
|
193
|
+
for (let index = 0; index < lines.length; index++) {
|
|
194
|
+
const lineText = lines[index] ?? '';
|
|
195
|
+
const trimmed = lineText.trim();
|
|
196
|
+
if (trimmed.startsWith('```')) {
|
|
197
|
+
inCodeBlock = !inCodeBlock;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (inCodeBlock) {
|
|
201
|
+
formatted.push({ kind: 'code', text: lineText });
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!trimmed) {
|
|
205
|
+
formatted.push({ kind: 'blank', text: '' });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (/^<{3,}/.test(trimmed)) {
|
|
209
|
+
formatted.push({ kind: 'conflict-start', text: trimmed });
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (/^={3,}$/.test(trimmed)) {
|
|
213
|
+
formatted.push({ kind: 'conflict-sep', text: trimmed });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (/^>{3,}/.test(trimmed)) {
|
|
217
|
+
formatted.push({ kind: 'conflict-end', text: trimmed });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const tableBlock = parseMarkdownTableBlock(lines, index);
|
|
221
|
+
if (tableBlock) {
|
|
222
|
+
formatted.push({ kind: 'table', table: tableBlock.table, text: '' });
|
|
223
|
+
index = tableBlock.nextIndex - 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const headingMatch = trimmed.match(/^(#{1,4})\s+(.*)$/);
|
|
227
|
+
if (headingMatch) {
|
|
228
|
+
formatted.push({ kind: 'heading', text: headingMatch[2] ?? '' });
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const numberedMatch = trimmed.match(/^(\d+)\.\s+(.*)$/);
|
|
232
|
+
if (numberedMatch) {
|
|
233
|
+
formatted.push({
|
|
234
|
+
kind: 'numbered',
|
|
235
|
+
marker: numberedMatch[1],
|
|
236
|
+
text: numberedMatch[2] ?? '',
|
|
237
|
+
});
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const bulletMatch = trimmed.match(/^[-*]\s+(.*)$/);
|
|
241
|
+
if (bulletMatch) {
|
|
242
|
+
formatted.push({ kind: 'bullet', text: bulletMatch[1] ?? '' });
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const quoteMatch = trimmed.match(/^>\s+(.*)$/);
|
|
246
|
+
if (quoteMatch) {
|
|
247
|
+
formatted.push({ kind: 'quote', text: quoteMatch[1] ?? '' });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
formatted.push({ kind: 'paragraph', text: lineText });
|
|
251
|
+
}
|
|
252
|
+
return formatted;
|
|
253
|
+
}
|
|
254
|
+
function segmentStyle(segment, bodyColor) {
|
|
255
|
+
if (segment.kind === 'bold') {
|
|
256
|
+
return { color: bodyColor, bold: true };
|
|
257
|
+
}
|
|
258
|
+
if (segment.kind === 'code') {
|
|
259
|
+
return { color: 'cyan' };
|
|
260
|
+
}
|
|
261
|
+
if (segment.kind === 'link') {
|
|
262
|
+
return { color: 'cyan', underline: true, linkUrl: segment.url };
|
|
263
|
+
}
|
|
264
|
+
return { color: bodyColor };
|
|
265
|
+
}
|
|
266
|
+
function styleFromSpan(part) {
|
|
267
|
+
const { text, ...style } = part;
|
|
268
|
+
void text;
|
|
269
|
+
return style;
|
|
270
|
+
}
|
|
271
|
+
function wrapInlineToLines(text, width, bodyColor, prefix = '') {
|
|
272
|
+
const safeWidth = Math.max(1, width);
|
|
273
|
+
const indent = prefix ? span(prefix, { color: 'gray' }) : null;
|
|
274
|
+
const indentSpaces = prefix ? span(' '.repeat(prefix.length), { color: 'gray' }) : null;
|
|
275
|
+
const segments = parseInlineSegments(text);
|
|
276
|
+
const rows = [[]];
|
|
277
|
+
let rowWidth = 0;
|
|
278
|
+
const startRow = () => {
|
|
279
|
+
rows.push([]);
|
|
280
|
+
rowWidth = 0;
|
|
281
|
+
};
|
|
282
|
+
const appendSpan = (part) => {
|
|
283
|
+
const current = rows[rows.length - 1];
|
|
284
|
+
const limit = safeWidth - (indent ? prefix.length : 0);
|
|
285
|
+
let remaining = part.text;
|
|
286
|
+
while (remaining.length > 0) {
|
|
287
|
+
const room = limit - rowWidth;
|
|
288
|
+
if (room <= 0) {
|
|
289
|
+
startRow();
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (remaining.length <= room) {
|
|
293
|
+
current.push(span(remaining, styleFromSpan(part)));
|
|
294
|
+
rowWidth += remaining.length;
|
|
295
|
+
remaining = '';
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
current.push(span(remaining.slice(0, room), styleFromSpan(part)));
|
|
299
|
+
remaining = remaining.slice(room);
|
|
300
|
+
startRow();
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
for (const segment of segments) {
|
|
304
|
+
const style = segmentStyle(segment, bodyColor);
|
|
305
|
+
const tokens = segment.text.split(/(\s+)/).filter((token) => token.length > 0);
|
|
306
|
+
for (const token of tokens) {
|
|
307
|
+
appendSpan(span(token, style));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return rows.map((spans, index) => {
|
|
311
|
+
const bodySpans = spans.length > 0 ? spans : [span(' ', {})];
|
|
312
|
+
if (indent && index === 0)
|
|
313
|
+
return line(indent, ...bodySpans);
|
|
314
|
+
if (indentSpaces)
|
|
315
|
+
return line(indentSpaces, ...bodySpans);
|
|
316
|
+
return line(...bodySpans);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function padCell(text, width) {
|
|
320
|
+
const plain = stripInlineFormattingForWidth(text);
|
|
321
|
+
if (plain.length >= width)
|
|
322
|
+
return plain.slice(0, width);
|
|
323
|
+
return plain + ' '.repeat(width - plain.length);
|
|
324
|
+
}
|
|
325
|
+
function renderTableLines(table, width) {
|
|
326
|
+
const columnWidths = fitTableColumnWidths(table.columnWidths, width);
|
|
327
|
+
const renderRow = (cells, bold) => {
|
|
328
|
+
const parts = columnWidths.map((colWidth, index) => span(` ${padCell(cells[index] ?? '', colWidth)} `, {
|
|
329
|
+
color: 'cyan',
|
|
330
|
+
bold,
|
|
331
|
+
}));
|
|
332
|
+
return line(span('│', { color: 'cyan' }), ...parts, span('│', { color: 'cyan' }));
|
|
333
|
+
};
|
|
334
|
+
const separator = line(span('├' +
|
|
335
|
+
columnWidths.map((colWidth) => '─'.repeat(colWidth + 2)).join('┼') +
|
|
336
|
+
'┤', { color: 'cyan', dim: true }));
|
|
337
|
+
const top = line(span('┌' +
|
|
338
|
+
columnWidths.map((colWidth) => '─'.repeat(colWidth + 2)).join('┬') +
|
|
339
|
+
'┐', { color: 'cyan', dim: true }));
|
|
340
|
+
const bottom = line(span('└' +
|
|
341
|
+
columnWidths.map((colWidth) => '─'.repeat(colWidth + 2)).join('┴') +
|
|
342
|
+
'┘', { color: 'cyan', dim: true }));
|
|
343
|
+
return [
|
|
344
|
+
top,
|
|
345
|
+
renderRow(table.headers, true),
|
|
346
|
+
separator,
|
|
347
|
+
...table.rows.map((row) => renderRow(row, false)),
|
|
348
|
+
bottom,
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
function getEntryColor(kind) {
|
|
352
|
+
switch (kind) {
|
|
353
|
+
case 'assistant':
|
|
354
|
+
case 'diff':
|
|
355
|
+
return 'green';
|
|
356
|
+
case 'error':
|
|
357
|
+
return 'red';
|
|
358
|
+
case 'system':
|
|
359
|
+
return 'yellow';
|
|
360
|
+
case 'tool':
|
|
361
|
+
return 'blue';
|
|
362
|
+
case 'user':
|
|
363
|
+
return 'cyan';
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
export function renderFormattedBodyLines(body, width, kind) {
|
|
367
|
+
const formatted = splitFormattedLines(body);
|
|
368
|
+
const headingColor = kind === 'system' ? 'cyan' : getEntryColor(kind);
|
|
369
|
+
const bodyColor = kind === 'error' ? 'red' : undefined;
|
|
370
|
+
const bodyWidth = Math.max(1, width - 2);
|
|
371
|
+
const output = [];
|
|
372
|
+
for (let index = 0; index < formatted.length; index++) {
|
|
373
|
+
const formattedLine = formatted[index];
|
|
374
|
+
if (formattedLine.kind === 'blank') {
|
|
375
|
+
output.push(plainLine(''));
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (formattedLine.kind === 'heading') {
|
|
379
|
+
for (const wrapped of wrapText(formattedLine.text, bodyWidth)) {
|
|
380
|
+
output.push(plainLine(` ${wrapped}`, { color: headingColor, bold: true }));
|
|
381
|
+
}
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (formattedLine.kind === 'bullet') {
|
|
385
|
+
output.push(...wrapInlineToLines(formattedLine.text, bodyWidth, bodyColor, '• '));
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (formattedLine.kind === 'numbered') {
|
|
389
|
+
const marker = `${formattedLine.marker}. `;
|
|
390
|
+
output.push(...wrapInlineToLines(formattedLine.text, bodyWidth, bodyColor, marker));
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (formattedLine.kind === 'quote') {
|
|
394
|
+
output.push(...wrapInlineToLines(formattedLine.text, bodyWidth, bodyColor, '│ '));
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (formattedLine.kind === 'code') {
|
|
398
|
+
const prev = formatted[index - 1];
|
|
399
|
+
if (prev && prev.kind !== 'code' && prev.kind !== 'blank') {
|
|
400
|
+
output.push(plainLine(''));
|
|
401
|
+
}
|
|
402
|
+
for (const wrapped of wrapText(formattedLine.text || ' ', bodyWidth)) {
|
|
403
|
+
output.push(plainLine(` ${wrapped}`, { color: 'cyan' }));
|
|
404
|
+
}
|
|
405
|
+
const next = formatted[index + 1];
|
|
406
|
+
if (next && next.kind !== 'code' && next.kind !== 'blank') {
|
|
407
|
+
output.push(plainLine(''));
|
|
408
|
+
}
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (formattedLine.kind === 'table' && formattedLine.table) {
|
|
412
|
+
output.push(...renderTableLines(formattedLine.table, bodyWidth).map((tableLine) => {
|
|
413
|
+
const spans = tableLine.spans.map((part) => ({
|
|
414
|
+
...part,
|
|
415
|
+
text: ` ${part.text}`,
|
|
416
|
+
}));
|
|
417
|
+
return { spans };
|
|
418
|
+
}));
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (formattedLine.kind === 'conflict-start') {
|
|
422
|
+
output.push(plainLine(' ━━ before ━━━━━━━━━━━━━━━', { color: 'red', dim: true }));
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (formattedLine.kind === 'conflict-sep') {
|
|
426
|
+
output.push(plainLine(' ━━━━━━━━━━━━━━━━━━━━━━━━━', { color: 'gray', dim: true }));
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (formattedLine.kind === 'conflict-end') {
|
|
430
|
+
output.push(plainLine(' ━━ after ━━━━━━━━━━━━━━━━', { color: 'green', dim: true }));
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
output.push(...wrapInlineToLines(formattedLine.text, bodyWidth, bodyColor, ' '));
|
|
434
|
+
}
|
|
435
|
+
return output;
|
|
436
|
+
}
|
|
437
|
+
export function renderPreformattedBodyLines(body, width, kind) {
|
|
438
|
+
const bodyColor = kind === 'error' ? 'red' : undefined;
|
|
439
|
+
const bodyWidth = Math.max(1, width - 8);
|
|
440
|
+
const output = [];
|
|
441
|
+
const normalizedBody = String(body ?? '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
442
|
+
for (const rawLine of normalizedBody.split('\n')) {
|
|
443
|
+
if (rawLine === '') {
|
|
444
|
+
output.push(plainLine(''));
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
let remaining = rawLine;
|
|
448
|
+
while (remaining.length > bodyWidth) {
|
|
449
|
+
output.push(plainLine(` ${remaining.slice(0, bodyWidth)}`, { color: bodyColor }));
|
|
450
|
+
remaining = remaining.slice(bodyWidth);
|
|
451
|
+
}
|
|
452
|
+
output.push(plainLine(` ${remaining}`, { color: bodyColor }));
|
|
453
|
+
}
|
|
454
|
+
return output;
|
|
455
|
+
}
|