@thegitai/cli 1.0.0-preview.3 → 1.0.0-preview.31
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 +39 -6
- package/dist/bin/ai.js +142 -383
- package/dist/src/agent-mode.js +1 -6
- package/dist/src/api/auth.js +6 -4
- package/dist/src/api/browser-login.js +152 -37
- package/dist/src/api/chat.js +258 -38
- package/dist/src/api/contracts.js +55 -1
- package/dist/src/api/default-host.js +1 -0
- package/dist/src/api/http.js +69 -7
- package/dist/src/api/models.js +19 -10
- package/dist/src/background-jobs.js +2 -2
- package/dist/src/cli-args.js +19 -5
- package/dist/src/core/clipboard.js +7 -13
- package/dist/src/core/image-limits.js +56 -0
- package/dist/src/core/image-path-extractor.js +70 -3
- package/dist/src/core/session-image-store.js +199 -0
- package/dist/src/executor.js +25 -3
- package/dist/src/help-text.js +67 -18
- package/dist/src/permissions.js +243 -0
- package/dist/src/session-safety.js +0 -12
- package/dist/src/session-store.js +121 -20
- package/dist/src/session.js +14 -3
- package/dist/src/signin.js +58 -0
- package/dist/src/tool-executor.js +11 -46
- package/dist/src/tools/delete-file.js +15 -3
- package/dist/src/tools/index.js +13 -10
- package/dist/src/tools/patch-file.js +12 -26
- package/dist/src/tools/read-image-file.js +85 -0
- package/dist/src/tools/replace-document-text.js +28 -18
- package/dist/src/tools/restore-checkpoint.js +0 -1
- package/dist/src/tools/run-command.js +14 -71
- package/dist/src/tools/run-node-script.js +12 -81
- package/dist/src/tools/save-generated-image.js +120 -0
- package/dist/src/tools/str-replace.js +12 -26
- package/dist/src/tools/undo-edit.js +1 -6
- package/dist/src/tools/write-file.js +67 -11
- 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 +649 -164
- package/dist/src/ui/tui/bridge.js +10 -0
- package/dist/src/ui/tui/build-frame.js +453 -115
- package/dist/src/ui/tui/markdown-render.js +81 -73
- package/dist/src/ui/tui/shell-input.js +206 -63
- package/dist/src/ui/tui/terminal-theme.js +28 -0
- 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/ui/tui/user-input.js +568 -0
- package/dist/src/utils.js +9 -0
- package/package.json +29 -6
- package/dist/src/markdown-renderer.js +0 -112
- package/dist/src/project-index.js +0 -221
- package/dist/src/tools/code-intel.js +0 -472
- package/dist/src/tools/find-symbol.js +0 -70
- package/dist/src/tools/hover-symbol.js +0 -95
- package/dist/src/tools/list-symbols.js +0 -55
- package/dist/src/tools/search-code.js +0 -37
- package/dist/src/tools/signature-help.js +0 -118
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { line, plainLine, span, wrapText } from './text.js';
|
|
2
|
-
const
|
|
3
|
-
|
|
1
|
+
import { displayWidth, line, padToWidth, plainLine, sliceToWidth, span, wrapText, wrapToWidth, } from './text.js';
|
|
2
|
+
const MIN_TABLE_COLUMN_WIDTH = 3;
|
|
3
|
+
function tableRowOverhead(columnCount) {
|
|
4
|
+
return 3 * columnCount + 1;
|
|
5
|
+
}
|
|
4
6
|
function parseInlineSegments(text) {
|
|
5
7
|
const source = String(text ?? '');
|
|
6
8
|
const segments = [];
|
|
@@ -110,45 +112,37 @@ function stripInlineFormattingForWidth(text) {
|
|
|
110
112
|
.replace(/\*\*([^*]+)\*\*/g, '$1');
|
|
111
113
|
}
|
|
112
114
|
function fitTableColumnWidths(columnWidths, maxWidth) {
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
115
|
-
return
|
|
116
|
-
const
|
|
117
|
-
const budget = Math.
|
|
118
|
-
const total =
|
|
119
|
-
if (
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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--;
|
|
115
|
+
const columnCount = columnWidths.length;
|
|
116
|
+
if (columnCount === 0)
|
|
117
|
+
return [];
|
|
118
|
+
const natural = columnWidths.map((width) => Math.max(1, Math.floor(width)));
|
|
119
|
+
const budget = Math.floor(maxWidth) - tableRowOverhead(columnCount);
|
|
120
|
+
const total = natural.reduce((sum, width) => sum + width, 0);
|
|
121
|
+
if (budget >= total)
|
|
122
|
+
return natural;
|
|
123
|
+
const floorWidth = Math.max(1, Math.min(MIN_TABLE_COLUMN_WIDTH, Math.floor(budget / columnCount)));
|
|
124
|
+
const widths = natural.map((width) => Math.min(width, floorWidth));
|
|
125
|
+
let used = widths.reduce((sum, width) => sum + width, 0);
|
|
126
|
+
if (used > budget) {
|
|
127
|
+
const share = Math.max(1, Math.floor(budget / columnCount));
|
|
128
|
+
for (let index = 0; index < columnCount; index++)
|
|
129
|
+
widths[index] = share;
|
|
130
|
+
used = share * columnCount;
|
|
136
131
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
let remaining = budget - used;
|
|
133
|
+
while (remaining > 0) {
|
|
134
|
+
let grew = false;
|
|
135
|
+
for (let index = 0; index < columnCount && remaining > 0; index++) {
|
|
136
|
+
if (widths[index] >= natural[index])
|
|
141
137
|
continue;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
widths[index]++;
|
|
139
|
+
remaining--;
|
|
140
|
+
grew = true;
|
|
145
141
|
}
|
|
146
|
-
if (
|
|
142
|
+
if (!grew)
|
|
147
143
|
break;
|
|
148
|
-
scaled[targetIndex]--;
|
|
149
|
-
remaining++;
|
|
150
144
|
}
|
|
151
|
-
return
|
|
145
|
+
return widths;
|
|
152
146
|
}
|
|
153
147
|
function normalizeMarkdownTableCells(cells, columnCount) {
|
|
154
148
|
return Array.from({ length: columnCount }, (_, index) => cells[index] ?? '');
|
|
@@ -176,7 +170,7 @@ function parseMarkdownTableBlock(lines, startIndex) {
|
|
|
176
170
|
const normalizedHeaders = normalizeMarkdownTableCells(headers, columnCount);
|
|
177
171
|
const columnWidths = normalizedHeaders.map((header, columnIndex) => {
|
|
178
172
|
const values = [header, ...rows.map((row) => row[columnIndex] ?? '')];
|
|
179
|
-
return Math.max(
|
|
173
|
+
return Math.max(MIN_TABLE_COLUMN_WIDTH, ...values.map((value) => displayWidth(stripInlineFormattingForWidth(value))));
|
|
180
174
|
});
|
|
181
175
|
return {
|
|
182
176
|
nextIndex,
|
|
@@ -280,24 +274,42 @@ function wrapInlineToLines(text, width, bodyColor, prefix = '') {
|
|
|
280
274
|
rowWidth = 0;
|
|
281
275
|
};
|
|
282
276
|
const appendSpan = (part) => {
|
|
283
|
-
|
|
284
|
-
const limit = safeWidth - (indent ? prefix.length : 0);
|
|
277
|
+
let current = rows[rows.length - 1];
|
|
285
278
|
let remaining = part.text;
|
|
286
279
|
while (remaining.length > 0) {
|
|
280
|
+
const limit = safeWidth - (rows.length === 1 && indent ? displayWidth(prefix) : 0);
|
|
287
281
|
const room = limit - rowWidth;
|
|
288
282
|
if (room <= 0) {
|
|
289
283
|
startRow();
|
|
284
|
+
current = rows[rows.length - 1];
|
|
290
285
|
continue;
|
|
291
286
|
}
|
|
292
|
-
|
|
287
|
+
const width = displayWidth(remaining);
|
|
288
|
+
if (width <= room) {
|
|
293
289
|
current.push(span(remaining, styleFromSpan(part)));
|
|
294
|
-
rowWidth +=
|
|
290
|
+
rowWidth += width;
|
|
295
291
|
remaining = '';
|
|
296
292
|
break;
|
|
297
293
|
}
|
|
298
|
-
|
|
299
|
-
|
|
294
|
+
if (rowWidth > 0) {
|
|
295
|
+
if (/^\s+$/.test(remaining)) {
|
|
296
|
+
remaining = '';
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
startRow();
|
|
300
|
+
current = rows[rows.length - 1];
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const head = sliceToWidth(remaining, room);
|
|
304
|
+
if (!head || (displayWidth(head) > room && current.length > 0)) {
|
|
305
|
+
startRow();
|
|
306
|
+
current = rows[rows.length - 1];
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
current.push(span(head, styleFromSpan(part)));
|
|
310
|
+
remaining = remaining.slice(head.length);
|
|
300
311
|
startRow();
|
|
312
|
+
current = rows[rows.length - 1];
|
|
301
313
|
}
|
|
302
314
|
};
|
|
303
315
|
for (const segment of segments) {
|
|
@@ -316,36 +328,35 @@ function wrapInlineToLines(text, width, bodyColor, prefix = '') {
|
|
|
316
328
|
return line(...bodySpans);
|
|
317
329
|
});
|
|
318
330
|
}
|
|
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
331
|
function renderTableLines(table, width) {
|
|
326
332
|
const columnWidths = fitTableColumnWidths(table.columnWidths, width);
|
|
333
|
+
const border = (left, joint, right) => line(span(left +
|
|
334
|
+
columnWidths.map((colWidth) => '─'.repeat(colWidth + 2)).join(joint) +
|
|
335
|
+
right, { color: 'cyan', dim: true }));
|
|
327
336
|
const renderRow = (cells, bold) => {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
337
|
+
const wrapped = columnWidths.map((colWidth, index) => wrapToWidth(stripInlineFormattingForWidth(cells[index] ?? ''), colWidth));
|
|
338
|
+
const height = Math.max(1, ...wrapped.map((cellLines) => cellLines.length));
|
|
339
|
+
const rows = [];
|
|
340
|
+
for (let row = 0; row < height; row++) {
|
|
341
|
+
const spans = [span('│', { color: 'cyan' })];
|
|
342
|
+
for (let column = 0; column < columnWidths.length; column++) {
|
|
343
|
+
const text = wrapped[column]?.[row] ?? '';
|
|
344
|
+
spans.push(span(` ${padToWidth(text, columnWidths[column])} `, {
|
|
345
|
+
color: 'cyan',
|
|
346
|
+
bold,
|
|
347
|
+
}));
|
|
348
|
+
spans.push(span('│', { color: 'cyan' }));
|
|
349
|
+
}
|
|
350
|
+
rows.push(line(...spans));
|
|
351
|
+
}
|
|
352
|
+
return rows;
|
|
333
353
|
};
|
|
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
354
|
return [
|
|
344
|
-
|
|
345
|
-
renderRow(table.headers, true),
|
|
346
|
-
|
|
347
|
-
...table.rows.
|
|
348
|
-
|
|
355
|
+
border('┌', '┬', '┐'),
|
|
356
|
+
...renderRow(table.headers, true),
|
|
357
|
+
border('├', '┼', '┤'),
|
|
358
|
+
...table.rows.flatMap((row) => renderRow(row, false)),
|
|
359
|
+
border('└', '┴', '┘'),
|
|
349
360
|
];
|
|
350
361
|
}
|
|
351
362
|
function getEntryColor(kind) {
|
|
@@ -410,10 +421,7 @@ export function renderFormattedBodyLines(body, width, kind) {
|
|
|
410
421
|
}
|
|
411
422
|
if (formattedLine.kind === 'table' && formattedLine.table) {
|
|
412
423
|
output.push(...renderTableLines(formattedLine.table, bodyWidth).map((tableLine) => {
|
|
413
|
-
const spans = tableLine.spans.map((part) =>
|
|
414
|
-
...part,
|
|
415
|
-
text: ` ${part.text}`,
|
|
416
|
-
}));
|
|
424
|
+
const spans = tableLine.spans.map((part, index) => index === 0 ? { ...part, text: ` ${part.text}` } : part);
|
|
417
425
|
return { spans };
|
|
418
426
|
}));
|
|
419
427
|
continue;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
2
|
-
import {
|
|
2
|
+
import { MAX_IMAGES_PER_MESSAGE, MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE, approximateBase64DecodedBytes, totalAttachmentBytes, } from '../../core/image-limits.js';
|
|
3
|
+
import { tryCacheAttachmentBytes } from '../../core/session-image-store.js';
|
|
4
|
+
import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor, getInputCommandToken, getNextApprovalCursor, getNextModelPickerIndex, getSlashCommandSuggestions, insertAtCursor, isExactSlashCommandToken, navigatePromptHistory, shouldRemountLiveFrameForComposerInputChange, } from '../repl.js';
|
|
3
5
|
import { buildPastePlaceholder, shouldCollapsePaste, } from '../paste-collapse.js';
|
|
6
|
+
import { handleUserInputPromptEvent, } from './user-input.js';
|
|
7
|
+
const APPROVAL_PREVIEW_PAGE_ROWS = 3;
|
|
4
8
|
function isClipboardImagePasteKey(key) {
|
|
5
9
|
if (process.platform === 'win32') {
|
|
6
10
|
return ((key.ctrl || key.meta) &&
|
|
@@ -9,6 +13,38 @@ function isClipboardImagePasteKey(key) {
|
|
|
9
13
|
}
|
|
10
14
|
return key.ctrl && key.input === 'v' && !key.shift && !key.meta;
|
|
11
15
|
}
|
|
16
|
+
export const APPROVAL_INPUT_GUARD_MS = 500;
|
|
17
|
+
function approvalIsGuarded(state) {
|
|
18
|
+
const openedAt = state.approvalOpenedAt;
|
|
19
|
+
if (typeof openedAt !== 'number')
|
|
20
|
+
return false;
|
|
21
|
+
return Date.now() - openedAt < APPROVAL_INPUT_GUARD_MS;
|
|
22
|
+
}
|
|
23
|
+
function applyUserInputPromptEvent(store, handlers, event) {
|
|
24
|
+
const current = store.getState();
|
|
25
|
+
if (!current.userInputPrompt)
|
|
26
|
+
return false;
|
|
27
|
+
const outcome = handleUserInputPromptEvent(current.userInputPrompt, event, handlers.getUserInputViewport?.());
|
|
28
|
+
store.update((state) => ({
|
|
29
|
+
...state,
|
|
30
|
+
userInputPrompt: outcome.state,
|
|
31
|
+
}));
|
|
32
|
+
if (outcome.result) {
|
|
33
|
+
void handlers.onResolveUserInput?.(outcome.result);
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
function composerIsEmpty(state) {
|
|
38
|
+
return (!state.input &&
|
|
39
|
+
state.cursor === 0 &&
|
|
40
|
+
state.pastedChunks.length === 0 &&
|
|
41
|
+
state.promptHistoryCursor === null);
|
|
42
|
+
}
|
|
43
|
+
function composerHasDiscardableDraft(state) {
|
|
44
|
+
if (!composerIsEmpty(state))
|
|
45
|
+
return true;
|
|
46
|
+
return !state.busy && state.imageAttachments.length > 0;
|
|
47
|
+
}
|
|
12
48
|
function shouldShowCommandPalette(state) {
|
|
13
49
|
if (state.busy ||
|
|
14
50
|
state.exiting ||
|
|
@@ -74,6 +110,41 @@ function pasteTextFromClipboard(store, handlers) {
|
|
|
74
110
|
}
|
|
75
111
|
insertPastedText(store, handlers, text);
|
|
76
112
|
}
|
|
113
|
+
function scrollTranscript(store, handlers, delta) {
|
|
114
|
+
if (delta === 0)
|
|
115
|
+
return;
|
|
116
|
+
store.update((current) => {
|
|
117
|
+
const limit = handlers.getTranscriptScrollLimit?.() ??
|
|
118
|
+
current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
|
|
119
|
+
const next = current.transcriptScrollOffset + delta;
|
|
120
|
+
return {
|
|
121
|
+
...current,
|
|
122
|
+
transcriptScrollOffset: Math.max(0, Math.min(next, limit)),
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function scrollTranscriptTo(store, handlers, offset) {
|
|
127
|
+
store.update((current) => {
|
|
128
|
+
const limit = handlers.getTranscriptScrollLimit?.() ??
|
|
129
|
+
current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
|
|
130
|
+
return {
|
|
131
|
+
...current,
|
|
132
|
+
transcriptScrollOffset: Math.max(0, Math.min(Math.trunc(offset), limit)),
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function scrollApprovalPreview(store, handlers, delta) {
|
|
137
|
+
if (delta === 0)
|
|
138
|
+
return;
|
|
139
|
+
store.update((current) => {
|
|
140
|
+
const limit = handlers.getApprovalScrollLimit?.() ?? 0;
|
|
141
|
+
const next = (current.approvalScrollOffset ?? 0) + delta;
|
|
142
|
+
return {
|
|
143
|
+
...current,
|
|
144
|
+
approvalScrollOffset: Math.max(0, Math.min(next, limit)),
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|
|
77
148
|
function filterResumeSessionsLocal(sessions, filter, serverModels) {
|
|
78
149
|
const q = filter.trim().toLowerCase();
|
|
79
150
|
if (!q)
|
|
@@ -88,6 +159,12 @@ function filterResumeSessionsLocal(sessions, filter, serverModels) {
|
|
|
88
159
|
}
|
|
89
160
|
export function handleShellKeyEvent(store, handlers, event) {
|
|
90
161
|
if (event.kind === 'paste') {
|
|
162
|
+
if (applyUserInputPromptEvent(store, handlers, {
|
|
163
|
+
kind: 'paste',
|
|
164
|
+
text: event.text,
|
|
165
|
+
})) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
91
168
|
insertPastedText(store, handlers, event.text);
|
|
92
169
|
return;
|
|
93
170
|
}
|
|
@@ -110,33 +187,67 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
110
187
|
return;
|
|
111
188
|
}
|
|
112
189
|
if (event.kind === 'contextMenu') {
|
|
190
|
+
if (store.getState().userInputPrompt) {
|
|
191
|
+
const text = (handlers.readClipboardText ?? readClipboardText)();
|
|
192
|
+
if (text) {
|
|
193
|
+
applyUserInputPromptEvent(store, handlers, { kind: 'paste', text });
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
113
197
|
pasteTextFromClipboard(store, handlers);
|
|
114
198
|
return;
|
|
115
199
|
}
|
|
116
200
|
if (event.kind === 'transcriptScroll') {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const next = current.transcriptScrollOffset + delta;
|
|
123
|
-
return {
|
|
124
|
-
...current,
|
|
125
|
-
transcriptScrollOffset: Math.max(0, Math.min(next, limit)),
|
|
126
|
-
};
|
|
127
|
-
});
|
|
128
|
-
}
|
|
201
|
+
scrollTranscript(store, handlers, Math.trunc(event.deltaLines));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (event.kind === 'transcriptScrollTo') {
|
|
205
|
+
scrollTranscriptTo(store, handlers, Number(event.offset ?? 0));
|
|
129
206
|
return;
|
|
130
207
|
}
|
|
131
208
|
if (event.kind !== 'key')
|
|
132
209
|
return;
|
|
133
210
|
const key = event;
|
|
134
211
|
const state = store.getState();
|
|
212
|
+
const commandPaletteActive = shouldShowCommandPalette(state);
|
|
213
|
+
const commandSuggestions = commandPaletteActive
|
|
214
|
+
? getSlashCommandSuggestions(state.input)
|
|
215
|
+
: [];
|
|
216
|
+
const prepareForComposerInputChange = (current, nextInput) => {
|
|
217
|
+
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
218
|
+
handlers.onLiveFrameShapeChange();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
135
221
|
if (key.ctrl && key.input === 'c') {
|
|
222
|
+
if (state.userInputPrompt) {
|
|
223
|
+
handlers.onCtrlC?.();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
136
226
|
if (state.sudoPrompt) {
|
|
137
227
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
138
228
|
return;
|
|
139
229
|
}
|
|
230
|
+
if (state.queuedMessage) {
|
|
231
|
+
handlers.onLiveFrameShapeChange();
|
|
232
|
+
store.update((current) => ({ ...current, queuedMessage: null }));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (composerHasDiscardableDraft(state)) {
|
|
236
|
+
store.update((current) => {
|
|
237
|
+
prepareForComposerInputChange(current, '');
|
|
238
|
+
return {
|
|
239
|
+
...current,
|
|
240
|
+
commandCursor: 0,
|
|
241
|
+
cursor: 0,
|
|
242
|
+
imageAttachments: current.busy ? current.imageAttachments : [],
|
|
243
|
+
input: '',
|
|
244
|
+
pastedChunks: [],
|
|
245
|
+
promptHistoryCursor: null,
|
|
246
|
+
promptHistoryDraft: '',
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
140
251
|
if (handlers.onCtrlC) {
|
|
141
252
|
handlers.onCtrlC();
|
|
142
253
|
return;
|
|
@@ -144,15 +255,10 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
144
255
|
handlers.onRequestExit();
|
|
145
256
|
return;
|
|
146
257
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const prepareForComposerInputChange = (current, nextInput) => {
|
|
152
|
-
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
153
|
-
handlers.onLiveFrameShapeChange();
|
|
154
|
-
}
|
|
155
|
-
};
|
|
258
|
+
if (state.userInputPrompt &&
|
|
259
|
+
applyUserInputPromptEvent(store, handlers, key)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
156
262
|
if (state.sudoPrompt) {
|
|
157
263
|
if (key.escape) {
|
|
158
264
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
@@ -176,24 +282,32 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
176
282
|
return;
|
|
177
283
|
}
|
|
178
284
|
if (state.approvalPrompt) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
285
|
+
if (key.pageUp || key.pageDown) {
|
|
286
|
+
const delta = key.pageUp ? -APPROVAL_PREVIEW_PAGE_ROWS : APPROVAL_PREVIEW_PAGE_ROWS;
|
|
287
|
+
if (key.shift) {
|
|
288
|
+
scrollTranscript(store, handlers, key.pageUp ? 8 : -8);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
scrollApprovalPreview(store, handlers, delta);
|
|
292
|
+
}
|
|
186
293
|
return;
|
|
187
294
|
}
|
|
188
295
|
if (key.upArrow || key.downArrow) {
|
|
189
296
|
store.update((current) => ({
|
|
190
297
|
...current,
|
|
191
|
-
approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1),
|
|
298
|
+
approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1, current.approvalPrompt?.options?.length ?? 0),
|
|
192
299
|
}));
|
|
193
300
|
return;
|
|
194
301
|
}
|
|
302
|
+
if (approvalIsGuarded(state)) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (key.escape) {
|
|
306
|
+
void handlers.onResolveApproval(-1);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
195
309
|
if (key.returnKey) {
|
|
196
|
-
void handlers.onResolveApproval(
|
|
310
|
+
void handlers.onResolveApproval(state.approvalCursor);
|
|
197
311
|
}
|
|
198
312
|
return;
|
|
199
313
|
}
|
|
@@ -201,15 +315,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
201
315
|
return;
|
|
202
316
|
}
|
|
203
317
|
if (key.pageUp || key.pageDown) {
|
|
204
|
-
store.
|
|
205
|
-
const transcriptLines = handlers.getTranscriptScrollLimit?.() ??
|
|
206
|
-
current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
|
|
207
|
-
const next = current.transcriptScrollOffset + (key.pageUp ? 8 : -8);
|
|
208
|
-
return {
|
|
209
|
-
...current,
|
|
210
|
-
transcriptScrollOffset: Math.max(0, Math.min(next, transcriptLines)),
|
|
211
|
-
};
|
|
212
|
-
});
|
|
318
|
+
scrollTranscript(store, handlers, key.pageUp ? 8 : -8);
|
|
213
319
|
return;
|
|
214
320
|
}
|
|
215
321
|
if (state.jobsPickerOpen) {
|
|
@@ -317,10 +423,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
317
423
|
return;
|
|
318
424
|
}
|
|
319
425
|
store.update((current) => {
|
|
320
|
-
if (
|
|
321
|
-
current.cursor === 0 &&
|
|
322
|
-
current.pastedChunks.length === 0 &&
|
|
323
|
-
current.promptHistoryCursor === null) {
|
|
426
|
+
if (composerIsEmpty(current)) {
|
|
324
427
|
return current;
|
|
325
428
|
}
|
|
326
429
|
prepareForComposerInputChange(current, '');
|
|
@@ -340,6 +443,15 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
340
443
|
handlers.onCycleAgentMode();
|
|
341
444
|
return;
|
|
342
445
|
}
|
|
446
|
+
if (state.busy && state.queuedMessage) {
|
|
447
|
+
if (key.returnKey) {
|
|
448
|
+
void handlers.onFireQueuedMessage?.();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (!key.upArrow && !isClipboardImagePasteKey(key)) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
343
455
|
if (commandPaletteActive && (key.upArrow || key.downArrow)) {
|
|
344
456
|
store.update((current) => ({
|
|
345
457
|
...current,
|
|
@@ -348,7 +460,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
348
460
|
return;
|
|
349
461
|
}
|
|
350
462
|
if (key.upArrow) {
|
|
351
|
-
if (state.busy && state.
|
|
463
|
+
if (state.busy && state.queuedMessage) {
|
|
352
464
|
handlers.onLiveFrameShapeChange();
|
|
353
465
|
store.update((current) => {
|
|
354
466
|
const queued = current.queuedMessage;
|
|
@@ -467,12 +579,16 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
467
579
|
}
|
|
468
580
|
if (isClipboardImagePasteKey(key)) {
|
|
469
581
|
const current = store.getState();
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
582
|
+
const target = current.queuedMessage
|
|
583
|
+
? {
|
|
584
|
+
body: current.queuedMessage.body,
|
|
585
|
+
attachments: current.queuedMessage.imageAttachments,
|
|
586
|
+
}
|
|
587
|
+
: { body: current.input, attachments: current.imageAttachments };
|
|
588
|
+
const liveAttachments = target.attachments.filter((attachment) => target.body.includes(`[Image #${attachment.index}]`));
|
|
589
|
+
if (liveAttachments.length >= MAX_IMAGES_PER_MESSAGE) {
|
|
474
590
|
store.appendEntry({
|
|
475
|
-
body:
|
|
591
|
+
body: `Maximum of ${MAX_IMAGES_PER_MESSAGE} images per message. Send the current message first.`,
|
|
476
592
|
kind: 'error',
|
|
477
593
|
title: 'Image',
|
|
478
594
|
});
|
|
@@ -480,22 +596,49 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
480
596
|
}
|
|
481
597
|
try {
|
|
482
598
|
const clipResult = readClipboardImage();
|
|
599
|
+
if (totalAttachmentBytes(liveAttachments) +
|
|
600
|
+
approximateBase64DecodedBytes(clipResult.base64Data) >
|
|
601
|
+
MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE) {
|
|
602
|
+
store.appendEntry({
|
|
603
|
+
body: `This image would put the message over the ${Math.round(MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE / 1024 / 1024)}MB combined image limit. Send the current message first.`,
|
|
604
|
+
kind: 'error',
|
|
605
|
+
title: 'Image',
|
|
606
|
+
});
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
483
609
|
const idx = liveAttachments.length === 0
|
|
484
610
|
? 1
|
|
485
611
|
: Math.max(...liveAttachments.map((attachment) => attachment.index)) + 1;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
612
|
+
const cached = tryCacheAttachmentBytes({
|
|
613
|
+
base64Data: clipResult.base64Data,
|
|
614
|
+
mimeType: clipResult.mimeType,
|
|
615
|
+
});
|
|
616
|
+
const attachment = {
|
|
617
|
+
index: cached?.index ?? idx,
|
|
618
|
+
mimeType: clipResult.mimeType,
|
|
619
|
+
base64Data: clipResult.base64Data,
|
|
620
|
+
source: 'clipboard',
|
|
621
|
+
...(cached ? { cachePath: cached.cachePath } : {}),
|
|
622
|
+
};
|
|
623
|
+
const marker = attachment.index;
|
|
624
|
+
store.update((shellState) => {
|
|
625
|
+
const nextAttachments = [...liveAttachments, attachment];
|
|
626
|
+
if (shellState.queuedMessage) {
|
|
627
|
+
return {
|
|
628
|
+
...shellState,
|
|
629
|
+
queuedMessage: {
|
|
630
|
+
...shellState.queuedMessage,
|
|
631
|
+
body: `${shellState.queuedMessage.body} [Image #${marker}]`.trim(),
|
|
632
|
+
imageAttachments: nextAttachments,
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
...shellState,
|
|
638
|
+
imageAttachments: nextAttachments,
|
|
639
|
+
...insertAtCursor(shellState, `[Image #${marker}] `),
|
|
640
|
+
};
|
|
641
|
+
});
|
|
499
642
|
}
|
|
500
643
|
catch (error) {
|
|
501
644
|
if (error?.code === 'NO_IMAGE') {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function isDarkTerminalBackground(env = process.env) {
|
|
2
|
+
const raw = String(env.COLORFGBG ?? '').trim();
|
|
3
|
+
if (!raw)
|
|
4
|
+
return false;
|
|
5
|
+
const parts = raw
|
|
6
|
+
.split(/[;:]/)
|
|
7
|
+
.map((part) => part.trim())
|
|
8
|
+
.filter(Boolean);
|
|
9
|
+
const bgToken = parts[parts.length - 1];
|
|
10
|
+
if (!bgToken)
|
|
11
|
+
return false;
|
|
12
|
+
const bg = Number(bgToken);
|
|
13
|
+
if (!Number.isFinite(bg))
|
|
14
|
+
return false;
|
|
15
|
+
return bg >= 0 && bg < 7;
|
|
16
|
+
}
|
|
17
|
+
export function mutedStyle(env = process.env) {
|
|
18
|
+
if (isDarkTerminalBackground(env)) {
|
|
19
|
+
return { color: 'ansi256(247)' };
|
|
20
|
+
}
|
|
21
|
+
return { color: 'gray' };
|
|
22
|
+
}
|
|
23
|
+
export function mutedColor(env = process.env) {
|
|
24
|
+
if (isDarkTerminalBackground(env)) {
|
|
25
|
+
return 'ansi256(247)';
|
|
26
|
+
}
|
|
27
|
+
return 'gray';
|
|
28
|
+
}
|
|
@@ -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`);
|