@thegitai/cli 1.0.0-preview.32 → 1.0.0-preview.34
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/dist/src/ui/repl.js
CHANGED
|
@@ -4,6 +4,7 @@ import { approvalScrollLimit, buildTuiFrame, formatJobElapsed, formatTodoProgres
|
|
|
4
4
|
import { createTerminalTitleController } from './tui/terminal-title.js';
|
|
5
5
|
import { captureTerminalWrites, releaseTerminalWrites, } from './tui/terminal-writes.js';
|
|
6
6
|
export { getSlashCommandSuggestions } from './tui/build-frame.js';
|
|
7
|
+
import { composerInputWidth, frameContentWidth } from './tui/composer-layout.js';
|
|
7
8
|
import { agentModeLabel, nextAgentMode, } from '../agent-mode.js';
|
|
8
9
|
import { chat, models } from '../api/index.js';
|
|
9
10
|
import { isTurnCancelledError } from '../api/chat.js';
|
|
@@ -2951,11 +2952,12 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2951
2952
|
};
|
|
2952
2953
|
const shellInputHandlers = {
|
|
2953
2954
|
getApprovalScrollLimit: () => {
|
|
2954
|
-
const contentWidth =
|
|
2955
|
+
const contentWidth = frameContentWidth(terminalCols);
|
|
2955
2956
|
return approvalScrollLimit(store.getState().approvalPrompt, contentWidth, terminalRows);
|
|
2956
2957
|
},
|
|
2958
|
+
getComposerInputWidth: () => composerInputWidth(terminalCols, store.getState()),
|
|
2957
2959
|
getTranscriptScrollLimit: () => {
|
|
2958
|
-
const contentWidth =
|
|
2960
|
+
const contentWidth = frameContentWidth(terminalCols);
|
|
2959
2961
|
const blocks = store.getState().transcript.map((entry) => renderTranscriptEntryLines(entry, contentWidth));
|
|
2960
2962
|
return blocks.reduce((total, block, index) => total + block.length + (index > 0 ? 1 : 0), 0);
|
|
2961
2963
|
},
|
|
@@ -5,6 +5,7 @@ import { renderFormattedBodyLines, renderPreformattedBodyLines, } from './markdo
|
|
|
5
5
|
import { displayWidth, line, padToWidth, plainLine, sliceToWidth, span, wrapText, } from './text.js';
|
|
6
6
|
import { isDarkTerminalBackground, mutedColor, mutedStyle } from './terminal-theme.js';
|
|
7
7
|
import { buildUserInputOverlayLines, } from './user-input.js';
|
|
8
|
+
import { composerPromptLabel, frameContentWidth, splitComposerInput, } from './composer-layout.js';
|
|
8
9
|
const WORKING_CLOCK_ICON = '◷';
|
|
9
10
|
const BRAILLE_SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴'];
|
|
10
11
|
const TODO_PANEL_MAX_ROWS = 12;
|
|
@@ -40,51 +41,6 @@ const MODEL_PICKER_BORDER_COLOR = 'cyan';
|
|
|
40
41
|
const MODEL_PICKER_ACCENT_COLOR = 'cyan';
|
|
41
42
|
const MODEL_PICKER_HIGHLIGHT_BG = 'ansi256(87)';
|
|
42
43
|
const MODEL_PICKER_META_INDENT = ' ';
|
|
43
|
-
function splitComposerInput(input, cursor, width) {
|
|
44
|
-
const safeWidth = Math.max(1, width);
|
|
45
|
-
const normalizedCursor = Math.min(Math.max(cursor, 0), input.length);
|
|
46
|
-
const rows = [''];
|
|
47
|
-
let cursorRow = 0;
|
|
48
|
-
let cursorCol = 0;
|
|
49
|
-
let capturedCursor = false;
|
|
50
|
-
const captureCursor = () => {
|
|
51
|
-
if (capturedCursor)
|
|
52
|
-
return;
|
|
53
|
-
const current = rows[rows.length - 1] ?? '';
|
|
54
|
-
if (current.length >= safeWidth) {
|
|
55
|
-
rows.push('');
|
|
56
|
-
cursorRow = rows.length - 1;
|
|
57
|
-
cursorCol = 0;
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
cursorRow = rows.length - 1;
|
|
61
|
-
cursorCol = current.length;
|
|
62
|
-
}
|
|
63
|
-
capturedCursor = true;
|
|
64
|
-
};
|
|
65
|
-
for (let index = 0; index < input.length; index += 1) {
|
|
66
|
-
const char = input[index] ?? '';
|
|
67
|
-
if (char !== '\n' &&
|
|
68
|
-
char !== '\r' &&
|
|
69
|
-
(rows[rows.length - 1]?.length ?? 0) >= safeWidth) {
|
|
70
|
-
rows.push('');
|
|
71
|
-
}
|
|
72
|
-
if (index === normalizedCursor) {
|
|
73
|
-
captureCursor();
|
|
74
|
-
}
|
|
75
|
-
if (char === '\r')
|
|
76
|
-
continue;
|
|
77
|
-
if (char === '\n') {
|
|
78
|
-
rows.push('');
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
rows[rows.length - 1] = `${rows[rows.length - 1] ?? ''}${char}`;
|
|
82
|
-
}
|
|
83
|
-
if (!capturedCursor) {
|
|
84
|
-
captureCursor();
|
|
85
|
-
}
|
|
86
|
-
return { cursorCol, cursorRow, rows };
|
|
87
|
-
}
|
|
88
44
|
function buildComposerInputLines(input, cursor, promptLabel, placeholder, width) {
|
|
89
45
|
if (!input) {
|
|
90
46
|
return [
|
|
@@ -1190,7 +1146,7 @@ function countSectionLines(sections) {
|
|
|
1190
1146
|
return sections.reduce((sum, section) => sum + section.lines.length, 0);
|
|
1191
1147
|
}
|
|
1192
1148
|
export function userInputViewportForFrame(state, cols, rows, spinnerFrame, elapsedSeconds, nowMs = 0) {
|
|
1193
|
-
const contentWidth =
|
|
1149
|
+
const contentWidth = frameContentWidth(cols);
|
|
1194
1150
|
const liveRows = buildLiveLines(state, contentWidth, spinnerFrame, elapsedSeconds, nowMs).length;
|
|
1195
1151
|
const overlayHeight = Math.max(0, rows - liveRows - composerFooterLines(state).length);
|
|
1196
1152
|
return {
|
|
@@ -1208,7 +1164,7 @@ function sliceTranscriptLines(lines, maxLines, scrollOffset) {
|
|
|
1208
1164
|
return lines.slice(start, start + maxLines);
|
|
1209
1165
|
}
|
|
1210
1166
|
export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, nowMs = 0) {
|
|
1211
|
-
const contentWidth =
|
|
1167
|
+
const contentWidth = frameContentWidth(cols);
|
|
1212
1168
|
const gutter = Math.max(Math.floor((cols - contentWidth) / 2), 0);
|
|
1213
1169
|
const buildTranscriptLines = (wrapWidth) => {
|
|
1214
1170
|
const blocks = state.transcript.map((entry) => cachedTranscriptEntryLines(entry, wrapWidth));
|
|
@@ -1240,7 +1196,7 @@ export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, n
|
|
|
1240
1196
|
: ' sends it to the agent now', { color: 'gray' }), span(' · ↑', { color: 'cyan', bold: true }), span(' edit', { color: 'gray' }), span(' · Esc', { color: 'cyan', bold: true }), span(' discard', { color: 'gray' })));
|
|
1241
1197
|
}
|
|
1242
1198
|
else {
|
|
1243
|
-
const promptLabel = state
|
|
1199
|
+
const promptLabel = composerPromptLabel(state);
|
|
1244
1200
|
const placeholder = state.busy
|
|
1245
1201
|
? 'Type to queue the next message'
|
|
1246
1202
|
: 'Type a request or /help';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const COMPOSER_PROMPT_LABEL_IDLE = '❯ ';
|
|
2
|
+
export const COMPOSER_PROMPT_LABEL_BUSY = 'queue> ';
|
|
3
|
+
export function composerPromptLabel(state) {
|
|
4
|
+
return state.busy ? COMPOSER_PROMPT_LABEL_BUSY : COMPOSER_PROMPT_LABEL_IDLE;
|
|
5
|
+
}
|
|
6
|
+
export function frameContentWidth(cols) {
|
|
7
|
+
return Math.max(20, Math.floor(cols * 0.95) - 2);
|
|
8
|
+
}
|
|
9
|
+
export function composerInputWidth(cols, state) {
|
|
10
|
+
return Math.max(1, frameContentWidth(cols) - composerPromptLabel(state).length);
|
|
11
|
+
}
|
|
12
|
+
function buildComposerLayout(input, cursor, width) {
|
|
13
|
+
const safeWidth = Math.max(1, width);
|
|
14
|
+
const normalizedCursor = Math.min(Math.max(cursor, 0), input.length);
|
|
15
|
+
const rows = [{ cursorOffsets: [0], text: '' }];
|
|
16
|
+
let cursorRow = 0;
|
|
17
|
+
let cursorCol = 0;
|
|
18
|
+
let capturedCursor = false;
|
|
19
|
+
const currentRow = () => rows[rows.length - 1];
|
|
20
|
+
const addRow = (offset) => {
|
|
21
|
+
rows.push({ cursorOffsets: [offset], text: '' });
|
|
22
|
+
};
|
|
23
|
+
const captureCursor = () => {
|
|
24
|
+
if (capturedCursor)
|
|
25
|
+
return;
|
|
26
|
+
if (currentRow().text.length >= safeWidth) {
|
|
27
|
+
addRow(normalizedCursor);
|
|
28
|
+
}
|
|
29
|
+
cursorRow = rows.length - 1;
|
|
30
|
+
cursorCol = currentRow().text.length;
|
|
31
|
+
capturedCursor = true;
|
|
32
|
+
};
|
|
33
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
34
|
+
const char = input[index] ?? '';
|
|
35
|
+
if (char !== '\n' &&
|
|
36
|
+
char !== '\r' &&
|
|
37
|
+
currentRow().text.length >= safeWidth) {
|
|
38
|
+
addRow(index);
|
|
39
|
+
}
|
|
40
|
+
if (index === normalizedCursor) {
|
|
41
|
+
captureCursor();
|
|
42
|
+
}
|
|
43
|
+
if (char === '\r') {
|
|
44
|
+
currentRow().cursorOffsets[currentRow().text.length] = index + 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (char === '\n') {
|
|
48
|
+
currentRow().cursorOffsets[currentRow().text.length] =
|
|
49
|
+
input[index - 1] === '\r' ? index - 1 : index;
|
|
50
|
+
addRow(index + 1);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
currentRow().text += char;
|
|
54
|
+
currentRow().cursorOffsets.push(index + 1);
|
|
55
|
+
}
|
|
56
|
+
if (!capturedCursor) {
|
|
57
|
+
captureCursor();
|
|
58
|
+
}
|
|
59
|
+
return { cursorCol, cursorRow, rows };
|
|
60
|
+
}
|
|
61
|
+
export function splitComposerInput(input, cursor, width) {
|
|
62
|
+
const layout = buildComposerLayout(input, cursor, width);
|
|
63
|
+
return {
|
|
64
|
+
cursorCol: layout.cursorCol,
|
|
65
|
+
cursorRow: layout.cursorRow,
|
|
66
|
+
rows: layout.rows.map((row) => row.text),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function moveComposerCursorVertically(input, cursor, width, direction) {
|
|
70
|
+
const layout = buildComposerLayout(input, cursor, width);
|
|
71
|
+
const targetRow = layout.rows[layout.cursorRow + direction];
|
|
72
|
+
if (!targetRow)
|
|
73
|
+
return null;
|
|
74
|
+
const targetCol = Math.min(layout.cursorCol, targetRow.text.length);
|
|
75
|
+
return targetRow.cursorOffsets[targetCol] ?? null;
|
|
76
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { moveComposerCursorVertically } from './composer-layout.js';
|
|
1
2
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
2
3
|
import { MAX_IMAGES_PER_MESSAGE, MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE, approximateBase64DecodedBytes, totalAttachmentBytes, } from '../../core/image-limits.js';
|
|
3
4
|
import { tryCacheAttachmentBytes } from '../../core/session-image-store.js';
|
|
@@ -479,9 +480,13 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
479
480
|
});
|
|
480
481
|
return;
|
|
481
482
|
}
|
|
482
|
-
|
|
483
|
+
const nextCursor = moveComposerCursorVertically(state.input, state.cursor, handlers.getComposerInputWidth?.() ?? Number.POSITIVE_INFINITY, -1);
|
|
484
|
+
if (nextCursor === null && state.busy)
|
|
483
485
|
return;
|
|
484
486
|
store.update((current) => {
|
|
487
|
+
if (nextCursor !== null) {
|
|
488
|
+
return { ...current, commandCursor: 0, cursor: nextCursor };
|
|
489
|
+
}
|
|
485
490
|
const next = navigatePromptHistory(current, 'previous');
|
|
486
491
|
prepareForComposerInputChange(current, next.input);
|
|
487
492
|
return next;
|
|
@@ -489,9 +494,13 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
489
494
|
return;
|
|
490
495
|
}
|
|
491
496
|
if (key.downArrow) {
|
|
492
|
-
|
|
497
|
+
const nextCursor = moveComposerCursorVertically(state.input, state.cursor, handlers.getComposerInputWidth?.() ?? Number.POSITIVE_INFINITY, 1);
|
|
498
|
+
if (nextCursor === null && state.busy)
|
|
493
499
|
return;
|
|
494
500
|
store.update((current) => {
|
|
501
|
+
if (nextCursor !== null) {
|
|
502
|
+
return { ...current, commandCursor: 0, cursor: nextCursor };
|
|
503
|
+
}
|
|
495
504
|
const next = navigatePromptHistory(current, 'next');
|
|
496
505
|
prepareForComposerInputChange(current, next.input);
|
|
497
506
|
return next;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-preview.
|
|
3
|
+
"version": "1.0.0-preview.34",
|
|
4
4
|
"description": "TheGitAI is an agentic AI coding tool for your terminal. It reads and searches your repository, writes and edits files, runs your tests, and verifies the change before handing it back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentic-ai",
|
|
@@ -44,10 +44,11 @@
|
|
|
44
44
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
45
45
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
46
46
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
47
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
48
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
49
|
-
"@thegitai/tui-linux-
|
|
50
|
-
"@thegitai/tui-
|
|
47
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.34",
|
|
48
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.34",
|
|
49
|
+
"@thegitai/tui-linux-arm64": "1.0.0-preview.34",
|
|
50
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.34",
|
|
51
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.34",
|
|
51
52
|
"@vscode/ripgrep": "1.18.0"
|
|
52
53
|
},
|
|
53
54
|
"repository": {
|