@thegitai/cli 1.0.0-preview.1 → 1.0.0-preview.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/README.md +16 -4
- package/dist/bin/ai.js +57 -287
- package/dist/src/api/auth.js +2 -2
- package/dist/src/api/browser-login.js +72 -3
- package/dist/src/api/chat.js +147 -30
- package/dist/src/api/http.js +16 -3
- package/dist/src/api/models.js +9 -4
- package/dist/src/help-text.js +19 -7
- package/dist/src/patcher.js +96 -9
- package/dist/src/project-index.js +13 -1
- package/dist/src/project-orientation.js +99 -0
- package/dist/src/scratch-dir.js +51 -33
- package/dist/src/session-store.js +52 -20
- package/dist/src/session.js +8 -0
- package/dist/src/tool-executor.js +38 -6
- package/dist/src/tools/delete-file.js +22 -4
- package/dist/src/tools/patch-file.js +30 -5
- package/dist/src/tools/read-file.js +3 -1
- package/dist/src/tools/replace-document-text.js +7 -1
- package/dist/src/tools/run-command.js +37 -19
- package/dist/src/tools/run-node-script.js +24 -4
- package/dist/src/tools/str-replace.js +30 -5
- package/dist/src/tools/write-file.js +25 -5
- package/dist/src/turn-failure-marker.js +11 -0
- package/dist/src/ui/prompt-history-store.js +1 -1
- package/dist/src/ui/repl.js +197 -51
- package/dist/src/ui/tui/bridge.js +3 -0
- package/dist/src/ui/tui/build-frame.js +179 -82
- package/dist/src/ui/tui/markdown-render.js +72 -73
- package/dist/src/ui/tui/shell-input.js +42 -13
- package/dist/src/ui/tui/terminal-title.js +3 -0
- package/dist/src/ui/tui/terminal-writes.js +48 -0
- package/dist/src/ui/tui/text.js +158 -4
- package/dist/src/utils.js +9 -0
- package/package.json +18 -6
- package/dist/src/markdown-renderer.js +0 -112
|
@@ -9,6 +9,17 @@ function isClipboardImagePasteKey(key) {
|
|
|
9
9
|
}
|
|
10
10
|
return key.ctrl && key.input === 'v' && !key.shift && !key.meta;
|
|
11
11
|
}
|
|
12
|
+
function composerIsEmpty(state) {
|
|
13
|
+
return (!state.input &&
|
|
14
|
+
state.cursor === 0 &&
|
|
15
|
+
state.pastedChunks.length === 0 &&
|
|
16
|
+
state.promptHistoryCursor === null);
|
|
17
|
+
}
|
|
18
|
+
function composerHasDiscardableDraft(state) {
|
|
19
|
+
if (!composerIsEmpty(state))
|
|
20
|
+
return true;
|
|
21
|
+
return !state.busy && state.imageAttachments.length > 0;
|
|
22
|
+
}
|
|
12
23
|
function shouldShowCommandPalette(state) {
|
|
13
24
|
if (state.busy ||
|
|
14
25
|
state.exiting ||
|
|
@@ -132,11 +143,41 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
132
143
|
return;
|
|
133
144
|
const key = event;
|
|
134
145
|
const state = store.getState();
|
|
146
|
+
const commandPaletteActive = shouldShowCommandPalette(state);
|
|
147
|
+
const commandSuggestions = commandPaletteActive
|
|
148
|
+
? getSlashCommandSuggestions(state.input)
|
|
149
|
+
: [];
|
|
150
|
+
const prepareForComposerInputChange = (current, nextInput) => {
|
|
151
|
+
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
152
|
+
handlers.onLiveFrameShapeChange();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
135
155
|
if (key.ctrl && key.input === 'c') {
|
|
136
156
|
if (state.sudoPrompt) {
|
|
137
157
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
138
158
|
return;
|
|
139
159
|
}
|
|
160
|
+
if (state.queuedMessage) {
|
|
161
|
+
handlers.onLiveFrameShapeChange();
|
|
162
|
+
store.update((current) => ({ ...current, queuedMessage: null }));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (composerHasDiscardableDraft(state)) {
|
|
166
|
+
store.update((current) => {
|
|
167
|
+
prepareForComposerInputChange(current, '');
|
|
168
|
+
return {
|
|
169
|
+
...current,
|
|
170
|
+
commandCursor: 0,
|
|
171
|
+
cursor: 0,
|
|
172
|
+
imageAttachments: current.busy ? current.imageAttachments : [],
|
|
173
|
+
input: '',
|
|
174
|
+
pastedChunks: [],
|
|
175
|
+
promptHistoryCursor: null,
|
|
176
|
+
promptHistoryDraft: '',
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
140
181
|
if (handlers.onCtrlC) {
|
|
141
182
|
handlers.onCtrlC();
|
|
142
183
|
return;
|
|
@@ -144,15 +185,6 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
144
185
|
handlers.onRequestExit();
|
|
145
186
|
return;
|
|
146
187
|
}
|
|
147
|
-
const commandPaletteActive = shouldShowCommandPalette(state);
|
|
148
|
-
const commandSuggestions = commandPaletteActive
|
|
149
|
-
? getSlashCommandSuggestions(state.input)
|
|
150
|
-
: [];
|
|
151
|
-
const prepareForComposerInputChange = (current, nextInput) => {
|
|
152
|
-
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
153
|
-
handlers.onLiveFrameShapeChange();
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
188
|
if (state.sudoPrompt) {
|
|
157
189
|
if (key.escape) {
|
|
158
190
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
@@ -317,10 +349,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
317
349
|
return;
|
|
318
350
|
}
|
|
319
351
|
store.update((current) => {
|
|
320
|
-
if (
|
|
321
|
-
current.cursor === 0 &&
|
|
322
|
-
current.pastedChunks.length === 0 &&
|
|
323
|
-
current.promptHistoryCursor === null) {
|
|
352
|
+
if (composerIsEmpty(current)) {
|
|
324
353
|
return current;
|
|
325
354
|
}
|
|
326
355
|
prepareForComposerInputChange(current, '');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTuiMode } from '../../runtime-mode.js';
|
|
1
2
|
const BRAILLE_SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴'];
|
|
2
3
|
const TITLE_BRAND = 'TheGitAI';
|
|
3
4
|
const TITLE_MARK_PREFIX = '❯_';
|
|
@@ -19,6 +20,8 @@ export function formatTerminalTitle(state, spinnerFrame = 0) {
|
|
|
19
20
|
return `${TITLE_MARK_PREFIX}● ${TITLE_BRAND}`;
|
|
20
21
|
}
|
|
21
22
|
export function writeTerminalTitle(title, stream = process.stdout) {
|
|
23
|
+
if (isTuiMode())
|
|
24
|
+
return;
|
|
22
25
|
if (!('isTTY' in stream) || !stream.isTTY)
|
|
23
26
|
return;
|
|
24
27
|
stream.write(`\x1b]0;${title}\x07`);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const MAX_CAPTURED_CHARS = 1_000_000;
|
|
2
|
+
let restore = null;
|
|
3
|
+
let captured = [];
|
|
4
|
+
let capturedChars = 0;
|
|
5
|
+
function chunkToString(chunk, encoding) {
|
|
6
|
+
if (typeof chunk === 'string')
|
|
7
|
+
return chunk;
|
|
8
|
+
if (chunk instanceof Uint8Array) {
|
|
9
|
+
return Buffer.from(chunk).toString(typeof encoding === 'string' ? encoding : 'utf8');
|
|
10
|
+
}
|
|
11
|
+
return String(chunk ?? '');
|
|
12
|
+
}
|
|
13
|
+
export function captureTerminalWrites() {
|
|
14
|
+
if (restore)
|
|
15
|
+
return;
|
|
16
|
+
const streams = [process.stdout, process.stderr];
|
|
17
|
+
const originals = streams.map((stream) => stream.write.bind(stream));
|
|
18
|
+
for (const stream of streams) {
|
|
19
|
+
stream.write = (chunk, encoding, callback) => {
|
|
20
|
+
if (capturedChars < MAX_CAPTURED_CHARS) {
|
|
21
|
+
const text = chunkToString(chunk, encoding);
|
|
22
|
+
captured.push(text);
|
|
23
|
+
capturedChars += text.length;
|
|
24
|
+
}
|
|
25
|
+
const done = typeof encoding === 'function' ? encoding : callback;
|
|
26
|
+
if (typeof done === 'function')
|
|
27
|
+
done();
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
restore = () => {
|
|
32
|
+
streams.forEach((stream, index) => {
|
|
33
|
+
stream.write = originals[index];
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function releaseTerminalWrites() {
|
|
38
|
+
if (!restore)
|
|
39
|
+
return;
|
|
40
|
+
restore();
|
|
41
|
+
restore = null;
|
|
42
|
+
if (captured.length > 0) {
|
|
43
|
+
const text = captured.join('');
|
|
44
|
+
captured = [];
|
|
45
|
+
capturedChars = 0;
|
|
46
|
+
process.stderr.write(text);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/src/ui/tui/text.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
const CSI_PATTERN = /\u001B\[[0-9;?]*[ -\/]*[@-~]/g;
|
|
2
|
+
const OSC_PATTERN = /\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)?/g;
|
|
3
|
+
const CONTROL_CHAR_PATTERN = /[\u0000-\u0008\u000B-\u001F\u007F]/g;
|
|
4
|
+
export function stripControlCharacters(text) {
|
|
5
|
+
return String(text ?? '')
|
|
6
|
+
.replace(OSC_PATTERN, '')
|
|
7
|
+
.replace(CSI_PATTERN, '')
|
|
8
|
+
.replace(CONTROL_CHAR_PATTERN, '');
|
|
9
|
+
}
|
|
1
10
|
export function span(text, style = {}) {
|
|
2
|
-
return { text, ...style };
|
|
11
|
+
return { text: stripControlCharacters(text), ...style };
|
|
3
12
|
}
|
|
4
13
|
export function line(...spans) {
|
|
5
14
|
return { spans };
|
|
@@ -14,10 +23,11 @@ export function wrapText(text, width) {
|
|
|
14
23
|
const lines = [];
|
|
15
24
|
for (const rawLine of text.split('\n')) {
|
|
16
25
|
let remaining = rawLine;
|
|
17
|
-
while (remaining
|
|
18
|
-
|
|
26
|
+
while (displayWidth(remaining) > safeWidth) {
|
|
27
|
+
const head = sliceToWidth(remaining, safeWidth);
|
|
28
|
+
let breakAt = head.lastIndexOf(' ');
|
|
19
29
|
if (breakAt <= 0)
|
|
20
|
-
breakAt =
|
|
30
|
+
breakAt = head.length;
|
|
21
31
|
lines.push(remaining.slice(0, breakAt).trimEnd());
|
|
22
32
|
remaining = remaining.slice(breakAt).trimStart();
|
|
23
33
|
}
|
|
@@ -28,3 +38,147 @@ export function wrapText(text, width) {
|
|
|
28
38
|
export function joinLines(blocks) {
|
|
29
39
|
return blocks.flat();
|
|
30
40
|
}
|
|
41
|
+
function isZeroWidthCodePoint(codePoint) {
|
|
42
|
+
return (codePoint === 0x200d ||
|
|
43
|
+
(codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
44
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
45
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
46
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
47
|
+
(codePoint >= 0xfe00 && codePoint <= 0xfe0f) ||
|
|
48
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f));
|
|
49
|
+
}
|
|
50
|
+
function isWideCodePoint(codePoint) {
|
|
51
|
+
return ((codePoint >= 0x1100 && codePoint <= 0x115f) ||
|
|
52
|
+
codePoint === 0x231a ||
|
|
53
|
+
codePoint === 0x231b ||
|
|
54
|
+
(codePoint >= 0x23e9 && codePoint <= 0x23ec) ||
|
|
55
|
+
codePoint === 0x23f0 ||
|
|
56
|
+
codePoint === 0x23f3 ||
|
|
57
|
+
(codePoint >= 0x25fd && codePoint <= 0x25fe) ||
|
|
58
|
+
(codePoint >= 0x2614 && codePoint <= 0x2615) ||
|
|
59
|
+
(codePoint >= 0x2648 && codePoint <= 0x2653) ||
|
|
60
|
+
codePoint === 0x267f ||
|
|
61
|
+
codePoint === 0x2693 ||
|
|
62
|
+
codePoint === 0x26a1 ||
|
|
63
|
+
(codePoint >= 0x26aa && codePoint <= 0x26ab) ||
|
|
64
|
+
(codePoint >= 0x26bd && codePoint <= 0x26be) ||
|
|
65
|
+
(codePoint >= 0x26c4 && codePoint <= 0x26c5) ||
|
|
66
|
+
codePoint === 0x26ce ||
|
|
67
|
+
codePoint === 0x26d4 ||
|
|
68
|
+
codePoint === 0x26ea ||
|
|
69
|
+
(codePoint >= 0x26f2 && codePoint <= 0x26f3) ||
|
|
70
|
+
codePoint === 0x26f5 ||
|
|
71
|
+
codePoint === 0x26fa ||
|
|
72
|
+
codePoint === 0x26fd ||
|
|
73
|
+
codePoint === 0x2705 ||
|
|
74
|
+
(codePoint >= 0x270a && codePoint <= 0x270b) ||
|
|
75
|
+
codePoint === 0x2728 ||
|
|
76
|
+
codePoint === 0x274c ||
|
|
77
|
+
codePoint === 0x274e ||
|
|
78
|
+
(codePoint >= 0x2753 && codePoint <= 0x2755) ||
|
|
79
|
+
codePoint === 0x2757 ||
|
|
80
|
+
(codePoint >= 0x2795 && codePoint <= 0x2797) ||
|
|
81
|
+
codePoint === 0x27b0 ||
|
|
82
|
+
codePoint === 0x27bf ||
|
|
83
|
+
(codePoint >= 0x2b1b && codePoint <= 0x2b1c) ||
|
|
84
|
+
codePoint === 0x2b50 ||
|
|
85
|
+
codePoint === 0x2b55 ||
|
|
86
|
+
(codePoint >= 0x2e80 && codePoint <= 0x303e) ||
|
|
87
|
+
(codePoint >= 0x3041 && codePoint <= 0x33ff) ||
|
|
88
|
+
(codePoint >= 0x3400 && codePoint <= 0x4dbf) ||
|
|
89
|
+
(codePoint >= 0x4e00 && codePoint <= 0x9fff) ||
|
|
90
|
+
(codePoint >= 0xa000 && codePoint <= 0xa4cf) ||
|
|
91
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
92
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
93
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
94
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
95
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
96
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
97
|
+
(codePoint >= 0x1f680 && codePoint <= 0x1f6ff) ||
|
|
98
|
+
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
99
|
+
(codePoint >= 0x1fa70 && codePoint <= 0x1faff) ||
|
|
100
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd));
|
|
101
|
+
}
|
|
102
|
+
export function displayWidth(text) {
|
|
103
|
+
const chars = [...String(text ?? '')];
|
|
104
|
+
let width = 0;
|
|
105
|
+
for (let index = 0; index < chars.length; index++) {
|
|
106
|
+
const codePoint = chars[index].codePointAt(0);
|
|
107
|
+
if (isZeroWidthCodePoint(codePoint))
|
|
108
|
+
continue;
|
|
109
|
+
const next = chars[index + 1]?.codePointAt(0);
|
|
110
|
+
if (next === 0xfe0f) {
|
|
111
|
+
width += 2;
|
|
112
|
+
index++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (next === 0xfe0e) {
|
|
116
|
+
width += 1;
|
|
117
|
+
index++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
width += isWideCodePoint(codePoint) ? 2 : 1;
|
|
121
|
+
}
|
|
122
|
+
return width;
|
|
123
|
+
}
|
|
124
|
+
export function sliceToWidth(text, width) {
|
|
125
|
+
const limit = Math.max(1, Math.floor(width));
|
|
126
|
+
const chars = [...String(text ?? '')];
|
|
127
|
+
let out = '';
|
|
128
|
+
let used = 0;
|
|
129
|
+
for (let index = 0; index < chars.length; index++) {
|
|
130
|
+
const char = chars[index];
|
|
131
|
+
const next = chars[index + 1];
|
|
132
|
+
const selector = next === '️' || next === '︎';
|
|
133
|
+
const cluster = selector ? `${char}${next}` : char;
|
|
134
|
+
const clusterWidth = displayWidth(cluster);
|
|
135
|
+
if (used + clusterWidth > limit)
|
|
136
|
+
break;
|
|
137
|
+
out += cluster;
|
|
138
|
+
used += clusterWidth;
|
|
139
|
+
if (selector)
|
|
140
|
+
index++;
|
|
141
|
+
}
|
|
142
|
+
if (!out)
|
|
143
|
+
return chars[0] ?? '';
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
export function padToWidth(text, width) {
|
|
147
|
+
const current = displayWidth(text);
|
|
148
|
+
if (current >= width)
|
|
149
|
+
return text;
|
|
150
|
+
return text + ' '.repeat(width - current);
|
|
151
|
+
}
|
|
152
|
+
export function wrapToWidth(text, width) {
|
|
153
|
+
const limit = Math.max(1, Math.floor(width));
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const rawLine of String(text ?? '').split('\n')) {
|
|
156
|
+
let current = '';
|
|
157
|
+
const flush = () => {
|
|
158
|
+
out.push(current);
|
|
159
|
+
current = '';
|
|
160
|
+
};
|
|
161
|
+
for (const token of rawLine.split(/\s+/).filter(Boolean)) {
|
|
162
|
+
let rest = token;
|
|
163
|
+
while (displayWidth(rest) > limit) {
|
|
164
|
+
if (current)
|
|
165
|
+
flush();
|
|
166
|
+
const head = sliceToWidth(rest, limit);
|
|
167
|
+
out.push(head);
|
|
168
|
+
rest = rest.slice(head.length);
|
|
169
|
+
}
|
|
170
|
+
if (!rest)
|
|
171
|
+
continue;
|
|
172
|
+
const candidate = current ? `${current} ${rest}` : rest;
|
|
173
|
+
if (displayWidth(candidate) > limit) {
|
|
174
|
+
flush();
|
|
175
|
+
current = rest;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
current = candidate;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
flush();
|
|
182
|
+
}
|
|
183
|
+
return out.length > 0 ? out : [''];
|
|
184
|
+
}
|
package/dist/src/utils.js
CHANGED
|
@@ -17,6 +17,15 @@ export function truncate(text, maxChars = 4000) {
|
|
|
17
17
|
return text;
|
|
18
18
|
return `${text.slice(0, maxChars)}\n... (truncated)`;
|
|
19
19
|
}
|
|
20
|
+
export function singleLinePreview(text, maxChars) {
|
|
21
|
+
const collapsed = String(text ?? '')
|
|
22
|
+
.replace(/\n\.\.\. \(truncated\)\s*$/, '…')
|
|
23
|
+
.replace(/\s+/g, ' ')
|
|
24
|
+
.trim();
|
|
25
|
+
if (collapsed.length <= maxChars)
|
|
26
|
+
return collapsed;
|
|
27
|
+
return `${collapsed.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
|
28
|
+
}
|
|
20
29
|
export function clampInteger(value, fallback, max) {
|
|
21
30
|
const numeric = Number(value);
|
|
22
31
|
if (!Number.isFinite(numeric) || numeric <= 0) {
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-preview.
|
|
4
|
-
"description": "TheGitAI
|
|
3
|
+
"version": "1.0.0-preview.11",
|
|
4
|
+
"description": "TheGitAI is an AI coding agent for your terminal. It indexes your repository, writes and edits files, runs commands, and builds features with you.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"ai-coding-agent",
|
|
8
|
+
"coding-agent",
|
|
9
|
+
"coding-assistant",
|
|
10
|
+
"terminal",
|
|
11
|
+
"cli",
|
|
12
|
+
"developer-tools"
|
|
13
|
+
],
|
|
5
14
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
15
|
"homepage": "https://thegit.ai",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"email": "support@thegit.ai"
|
|
18
|
+
},
|
|
7
19
|
"type": "module",
|
|
8
20
|
"engines": {
|
|
9
21
|
"node": ">=24"
|
|
@@ -25,10 +37,10 @@
|
|
|
25
37
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
26
38
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
27
39
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
28
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
29
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
30
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
31
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
40
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.11",
|
|
41
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.11",
|
|
42
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.11",
|
|
43
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.11",
|
|
32
44
|
"@vscode/ripgrep": "1.18.0"
|
|
33
45
|
},
|
|
34
46
|
"publishConfig": {
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import chalk from './colors.js';
|
|
2
|
-
function renderInline(text) {
|
|
3
|
-
const parts = String(text ?? '').split(/(`[^`]+`)/g);
|
|
4
|
-
return parts
|
|
5
|
-
.map((part) => part.startsWith('`') && part.endsWith('`') && part.length > 1
|
|
6
|
-
? chalk.cyan(part.slice(1, -1))
|
|
7
|
-
: part)
|
|
8
|
-
.join('');
|
|
9
|
-
}
|
|
10
|
-
function isTableSeparator(line) {
|
|
11
|
-
const cells = splitTableRow(line);
|
|
12
|
-
return (cells.length > 1 &&
|
|
13
|
-
cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim())));
|
|
14
|
-
}
|
|
15
|
-
function splitTableRow(line) {
|
|
16
|
-
const trimmed = String(line ?? '').trim();
|
|
17
|
-
if (!trimmed.includes('|'))
|
|
18
|
-
return [];
|
|
19
|
-
return trimmed
|
|
20
|
-
.replace(/^\|/, '')
|
|
21
|
-
.replace(/\|$/, '')
|
|
22
|
-
.split('|')
|
|
23
|
-
.map((cell) => cell.trim());
|
|
24
|
-
}
|
|
25
|
-
function tableWidth(text) {
|
|
26
|
-
return text.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
27
|
-
}
|
|
28
|
-
function padCell(text, width) {
|
|
29
|
-
return `${text}${' '.repeat(Math.max(0, width - tableWidth(text)))}`;
|
|
30
|
-
}
|
|
31
|
-
function renderTable(rows) {
|
|
32
|
-
if (rows.length < 2 || !isTableSeparator(rows[1].join('|')))
|
|
33
|
-
return [];
|
|
34
|
-
const headers = rows[0];
|
|
35
|
-
const body = rows.slice(2).filter((row) => row.length > 0);
|
|
36
|
-
const columnCount = Math.max(headers.length, ...body.map((row) => row.length));
|
|
37
|
-
const normalizedRows = [headers, ...body].map((row) => Array.from({ length: columnCount }, (_, index) => renderInline(row[index] ?? '')));
|
|
38
|
-
const widths = Array.from({ length: columnCount }, (_, index) => Math.max(...normalizedRows.map((row) => tableWidth(row[index] ?? '')), 3));
|
|
39
|
-
const border = `+${widths.map((width) => '-'.repeat(width + 2)).join('+')}+`;
|
|
40
|
-
const renderRow = (row) => `| ${row.map((cell, index) => padCell(cell, widths[index])).join(' | ')} |`;
|
|
41
|
-
return [
|
|
42
|
-
border,
|
|
43
|
-
renderRow(normalizedRows[0].map((cell) => chalk.bold(cell))),
|
|
44
|
-
border,
|
|
45
|
-
...normalizedRows.slice(1).map(renderRow),
|
|
46
|
-
border,
|
|
47
|
-
];
|
|
48
|
-
}
|
|
49
|
-
function readTableBlock(lines, startIndex) {
|
|
50
|
-
if (startIndex + 1 >= lines.length)
|
|
51
|
-
return null;
|
|
52
|
-
const header = splitTableRow(lines[startIndex]);
|
|
53
|
-
const separator = splitTableRow(lines[startIndex + 1]);
|
|
54
|
-
if (header.length < 2 || separator.length < 2 || !isTableSeparator(lines[startIndex + 1])) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
const rows = [header, separator];
|
|
58
|
-
let index = startIndex + 2;
|
|
59
|
-
while (index < lines.length) {
|
|
60
|
-
const row = splitTableRow(lines[index]);
|
|
61
|
-
if (row.length < 2)
|
|
62
|
-
break;
|
|
63
|
-
rows.push(row);
|
|
64
|
-
index += 1;
|
|
65
|
-
}
|
|
66
|
-
const rendered = renderTable(rows);
|
|
67
|
-
return rendered.length ? { rendered, nextIndex: index } : null;
|
|
68
|
-
}
|
|
69
|
-
export function renderMarkdownForTerminal(markdown) {
|
|
70
|
-
const lines = String(markdown ?? '').replace(/\r\n?/g, '\n').split('\n');
|
|
71
|
-
const output = [];
|
|
72
|
-
let inCodeBlock = false;
|
|
73
|
-
for (let index = 0; index < lines.length; index++) {
|
|
74
|
-
const line = lines[index];
|
|
75
|
-
if (/^\s*```/.test(line)) {
|
|
76
|
-
inCodeBlock = !inCodeBlock;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (inCodeBlock) {
|
|
80
|
-
output.push(chalk.dim(` ${line}`));
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const table = readTableBlock(lines, index);
|
|
84
|
-
if (table) {
|
|
85
|
-
output.push(...table.rendered);
|
|
86
|
-
index = table.nextIndex - 1;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const heading = line.match(/^\s{0,3}#{1,6}\s+(.+)$/);
|
|
90
|
-
if (heading) {
|
|
91
|
-
output.push(chalk.bold(renderInline(heading[1].trim())));
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const bullet = line.match(/^(\s*)[-*]\s+(.+)$/);
|
|
95
|
-
if (bullet) {
|
|
96
|
-
output.push(`${bullet[1]}- ${renderInline(bullet[2].trim())}`);
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
const numbered = line.match(/^(\s*)\d+[.)]\s+(.+)$/);
|
|
100
|
-
if (numbered) {
|
|
101
|
-
output.push(`${numbered[1]}- ${renderInline(numbered[2].trim())}`);
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
const quote = line.match(/^\s*>\s?(.+)$/);
|
|
105
|
-
if (quote) {
|
|
106
|
-
output.push(chalk.dim(`> ${renderInline(quote[1].trim())}`));
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
output.push(renderInline(line));
|
|
110
|
-
}
|
|
111
|
-
return output.join('\n').trimEnd();
|
|
112
|
-
}
|