@thegitai/cli 1.0.0-preview.4 → 1.0.0-preview.40
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 +49 -4
- package/dist/bin/ai.js +119 -111
- package/dist/bin/browser-host.js +265 -0
- package/dist/src/agent-mode.js +11 -6
- package/dist/src/api/auth.js +6 -4
- package/dist/src/api/browser-login.js +152 -37
- package/dist/src/api/chat.js +286 -44
- package/dist/src/api/contracts.js +55 -1
- package/dist/src/api/default-host.js +1 -0
- package/dist/src/api/http.js +127 -7
- package/dist/src/api/models.js +29 -10
- package/dist/src/background-jobs.js +4 -2
- package/dist/src/browser/bridge.js +232 -0
- package/dist/src/browser/framing.js +42 -0
- package/dist/src/browser/native-host.js +209 -0
- package/dist/src/browser/protocol.js +46 -0
- package/dist/src/browser/session-bridge.js +104 -0
- package/dist/src/cli-args.js +19 -5
- package/dist/src/client-environment.js +2 -0
- 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 +246 -0
- package/dist/src/edit-journal.js +33 -2
- package/dist/src/executor.js +25 -3
- package/dist/src/help-text.js +58 -17
- package/dist/src/patcher.js +43 -4
- package/dist/src/permissions.js +288 -0
- package/dist/src/session-safety.js +0 -12
- package/dist/src/session-store.js +121 -20
- package/dist/src/session.js +19 -4
- package/dist/src/signin.js +58 -0
- package/dist/src/tool-executor.js +12 -46
- package/dist/src/tools/browser.js +628 -0
- package/dist/src/tools/delete-file.js +20 -4
- package/dist/src/tools/index.js +32 -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/ui/prompt-history-store.js +1 -1
- package/dist/src/ui/repl.js +802 -195
- package/dist/src/ui/tui/bridge.js +10 -0
- package/dist/src/ui/tui/build-frame.js +517 -165
- package/dist/src/ui/tui/composer-layout.js +76 -0
- package/dist/src/ui/tui/markdown-render.js +81 -73
- package/dist/src/ui/tui/shell-input.js +227 -67
- 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 +20 -8
- 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,11 @@
|
|
|
1
|
+
import { moveComposerCursorVertically } from './composer-layout.js';
|
|
1
2
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
2
|
-
import {
|
|
3
|
+
import { MAX_IMAGES_PER_MESSAGE, MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE, approximateBase64DecodedBytes, totalAttachmentBytes, } from '../../core/image-limits.js';
|
|
4
|
+
import { tryCacheAttachmentBytes } from '../../core/session-image-store.js';
|
|
5
|
+
import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor, getInputCommandToken, getNextApprovalCursor, getNextModelPickerIndex, getSlashCommandSuggestions, insertAtCursor, isExactSlashCommandToken, navigatePromptHistory, shouldRemountLiveFrameForComposerInputChange, } from '../repl.js';
|
|
3
6
|
import { buildPastePlaceholder, shouldCollapsePaste, } from '../paste-collapse.js';
|
|
7
|
+
import { handleUserInputPromptEvent, } from './user-input.js';
|
|
8
|
+
const APPROVAL_PREVIEW_PAGE_ROWS = 3;
|
|
4
9
|
function isClipboardImagePasteKey(key) {
|
|
5
10
|
if (process.platform === 'win32') {
|
|
6
11
|
return ((key.ctrl || key.meta) &&
|
|
@@ -9,6 +14,38 @@ function isClipboardImagePasteKey(key) {
|
|
|
9
14
|
}
|
|
10
15
|
return key.ctrl && key.input === 'v' && !key.shift && !key.meta;
|
|
11
16
|
}
|
|
17
|
+
export const APPROVAL_INPUT_GUARD_MS = 500;
|
|
18
|
+
function approvalIsGuarded(state) {
|
|
19
|
+
const openedAt = state.approvalOpenedAt;
|
|
20
|
+
if (typeof openedAt !== 'number')
|
|
21
|
+
return false;
|
|
22
|
+
return Date.now() - openedAt < APPROVAL_INPUT_GUARD_MS;
|
|
23
|
+
}
|
|
24
|
+
function applyUserInputPromptEvent(store, handlers, event) {
|
|
25
|
+
const current = store.getState();
|
|
26
|
+
if (!current.userInputPrompt)
|
|
27
|
+
return false;
|
|
28
|
+
const outcome = handleUserInputPromptEvent(current.userInputPrompt, event, handlers.getUserInputViewport?.());
|
|
29
|
+
store.update((state) => ({
|
|
30
|
+
...state,
|
|
31
|
+
userInputPrompt: outcome.state,
|
|
32
|
+
}));
|
|
33
|
+
if (outcome.result) {
|
|
34
|
+
void handlers.onResolveUserInput?.(outcome.result);
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function composerIsEmpty(state) {
|
|
39
|
+
return (!state.input &&
|
|
40
|
+
state.cursor === 0 &&
|
|
41
|
+
state.pastedChunks.length === 0 &&
|
|
42
|
+
state.promptHistoryCursor === null);
|
|
43
|
+
}
|
|
44
|
+
function composerHasDiscardableDraft(state) {
|
|
45
|
+
if (!composerIsEmpty(state))
|
|
46
|
+
return true;
|
|
47
|
+
return !state.busy && state.imageAttachments.length > 0;
|
|
48
|
+
}
|
|
12
49
|
function shouldShowCommandPalette(state) {
|
|
13
50
|
if (state.busy ||
|
|
14
51
|
state.exiting ||
|
|
@@ -74,6 +111,41 @@ function pasteTextFromClipboard(store, handlers) {
|
|
|
74
111
|
}
|
|
75
112
|
insertPastedText(store, handlers, text);
|
|
76
113
|
}
|
|
114
|
+
function scrollTranscript(store, handlers, delta) {
|
|
115
|
+
if (delta === 0)
|
|
116
|
+
return;
|
|
117
|
+
store.update((current) => {
|
|
118
|
+
const limit = handlers.getTranscriptScrollLimit?.() ??
|
|
119
|
+
current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
|
|
120
|
+
const next = current.transcriptScrollOffset + delta;
|
|
121
|
+
return {
|
|
122
|
+
...current,
|
|
123
|
+
transcriptScrollOffset: Math.max(0, Math.min(next, limit)),
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function scrollTranscriptTo(store, handlers, offset) {
|
|
128
|
+
store.update((current) => {
|
|
129
|
+
const limit = handlers.getTranscriptScrollLimit?.() ??
|
|
130
|
+
current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
|
|
131
|
+
return {
|
|
132
|
+
...current,
|
|
133
|
+
transcriptScrollOffset: Math.max(0, Math.min(Math.trunc(offset), limit)),
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function scrollApprovalPreview(store, handlers, delta) {
|
|
138
|
+
if (delta === 0)
|
|
139
|
+
return;
|
|
140
|
+
store.update((current) => {
|
|
141
|
+
const limit = handlers.getApprovalScrollLimit?.() ?? 0;
|
|
142
|
+
const next = (current.approvalScrollOffset ?? 0) + delta;
|
|
143
|
+
return {
|
|
144
|
+
...current,
|
|
145
|
+
approvalScrollOffset: Math.max(0, Math.min(next, limit)),
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|
|
77
149
|
function filterResumeSessionsLocal(sessions, filter, serverModels) {
|
|
78
150
|
const q = filter.trim().toLowerCase();
|
|
79
151
|
if (!q)
|
|
@@ -88,6 +160,12 @@ function filterResumeSessionsLocal(sessions, filter, serverModels) {
|
|
|
88
160
|
}
|
|
89
161
|
export function handleShellKeyEvent(store, handlers, event) {
|
|
90
162
|
if (event.kind === 'paste') {
|
|
163
|
+
if (applyUserInputPromptEvent(store, handlers, {
|
|
164
|
+
kind: 'paste',
|
|
165
|
+
text: event.text,
|
|
166
|
+
})) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
91
169
|
insertPastedText(store, handlers, event.text);
|
|
92
170
|
return;
|
|
93
171
|
}
|
|
@@ -110,33 +188,67 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
110
188
|
return;
|
|
111
189
|
}
|
|
112
190
|
if (event.kind === 'contextMenu') {
|
|
191
|
+
if (store.getState().userInputPrompt) {
|
|
192
|
+
const text = (handlers.readClipboardText ?? readClipboardText)();
|
|
193
|
+
if (text) {
|
|
194
|
+
applyUserInputPromptEvent(store, handlers, { kind: 'paste', text });
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
113
198
|
pasteTextFromClipboard(store, handlers);
|
|
114
199
|
return;
|
|
115
200
|
}
|
|
116
201
|
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
|
-
}
|
|
202
|
+
scrollTranscript(store, handlers, Math.trunc(event.deltaLines));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (event.kind === 'transcriptScrollTo') {
|
|
206
|
+
scrollTranscriptTo(store, handlers, Number(event.offset ?? 0));
|
|
129
207
|
return;
|
|
130
208
|
}
|
|
131
209
|
if (event.kind !== 'key')
|
|
132
210
|
return;
|
|
133
211
|
const key = event;
|
|
134
212
|
const state = store.getState();
|
|
213
|
+
const commandPaletteActive = shouldShowCommandPalette(state);
|
|
214
|
+
const commandSuggestions = commandPaletteActive
|
|
215
|
+
? getSlashCommandSuggestions(state.input)
|
|
216
|
+
: [];
|
|
217
|
+
const prepareForComposerInputChange = (current, nextInput) => {
|
|
218
|
+
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
219
|
+
handlers.onLiveFrameShapeChange();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
135
222
|
if (key.ctrl && key.input === 'c') {
|
|
223
|
+
if (state.userInputPrompt) {
|
|
224
|
+
handlers.onCtrlC?.();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
136
227
|
if (state.sudoPrompt) {
|
|
137
228
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
138
229
|
return;
|
|
139
230
|
}
|
|
231
|
+
if (state.queuedMessage) {
|
|
232
|
+
handlers.onLiveFrameShapeChange();
|
|
233
|
+
store.update((current) => ({ ...current, queuedMessage: null }));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (composerHasDiscardableDraft(state)) {
|
|
237
|
+
store.update((current) => {
|
|
238
|
+
prepareForComposerInputChange(current, '');
|
|
239
|
+
return {
|
|
240
|
+
...current,
|
|
241
|
+
commandCursor: 0,
|
|
242
|
+
cursor: 0,
|
|
243
|
+
imageAttachments: current.busy ? current.imageAttachments : [],
|
|
244
|
+
input: '',
|
|
245
|
+
pastedChunks: [],
|
|
246
|
+
promptHistoryCursor: null,
|
|
247
|
+
promptHistoryDraft: '',
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
140
252
|
if (handlers.onCtrlC) {
|
|
141
253
|
handlers.onCtrlC();
|
|
142
254
|
return;
|
|
@@ -144,15 +256,10 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
144
256
|
handlers.onRequestExit();
|
|
145
257
|
return;
|
|
146
258
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const prepareForComposerInputChange = (current, nextInput) => {
|
|
152
|
-
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
153
|
-
handlers.onLiveFrameShapeChange();
|
|
154
|
-
}
|
|
155
|
-
};
|
|
259
|
+
if (state.userInputPrompt &&
|
|
260
|
+
applyUserInputPromptEvent(store, handlers, key)) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
156
263
|
if (state.sudoPrompt) {
|
|
157
264
|
if (key.escape) {
|
|
158
265
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
@@ -176,24 +283,32 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
176
283
|
return;
|
|
177
284
|
}
|
|
178
285
|
if (state.approvalPrompt) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
286
|
+
if (key.pageUp || key.pageDown) {
|
|
287
|
+
const delta = key.pageUp ? -APPROVAL_PREVIEW_PAGE_ROWS : APPROVAL_PREVIEW_PAGE_ROWS;
|
|
288
|
+
if (key.shift) {
|
|
289
|
+
scrollTranscript(store, handlers, key.pageUp ? 8 : -8);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
scrollApprovalPreview(store, handlers, delta);
|
|
293
|
+
}
|
|
186
294
|
return;
|
|
187
295
|
}
|
|
188
296
|
if (key.upArrow || key.downArrow) {
|
|
189
297
|
store.update((current) => ({
|
|
190
298
|
...current,
|
|
191
|
-
approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1),
|
|
299
|
+
approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1, current.approvalPrompt?.options?.length ?? 0),
|
|
192
300
|
}));
|
|
193
301
|
return;
|
|
194
302
|
}
|
|
303
|
+
if (approvalIsGuarded(state)) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (key.escape) {
|
|
307
|
+
void handlers.onResolveApproval(-1);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
195
310
|
if (key.returnKey) {
|
|
196
|
-
void handlers.onResolveApproval(
|
|
311
|
+
void handlers.onResolveApproval(state.approvalCursor);
|
|
197
312
|
}
|
|
198
313
|
return;
|
|
199
314
|
}
|
|
@@ -201,24 +316,18 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
201
316
|
return;
|
|
202
317
|
}
|
|
203
318
|
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
|
-
});
|
|
319
|
+
scrollTranscript(store, handlers, key.pageUp ? 8 : -8);
|
|
213
320
|
return;
|
|
214
321
|
}
|
|
215
322
|
if (state.jobsPickerOpen) {
|
|
216
|
-
if (key.escape) {
|
|
323
|
+
if (key.escape || (key.ctrl && key.input === 'b' && !key.meta && !key.shift)) {
|
|
324
|
+
handlers.onLiveFrameShapeChange();
|
|
217
325
|
store.update((current) => ({
|
|
218
326
|
...current,
|
|
219
327
|
jobsPickerExpandedId: null,
|
|
220
328
|
jobsPickerOpen: false,
|
|
221
|
-
|
|
329
|
+
jobsPickerReturnStatus: null,
|
|
330
|
+
status: current.jobsPickerReturnStatus ?? 'Ready',
|
|
222
331
|
}));
|
|
223
332
|
return;
|
|
224
333
|
}
|
|
@@ -306,6 +415,12 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
306
415
|
}
|
|
307
416
|
return;
|
|
308
417
|
}
|
|
418
|
+
if (key.ctrl && key.input === 'b' && !key.meta && !key.shift) {
|
|
419
|
+
if ((state.backgroundJobs ?? []).length > 0) {
|
|
420
|
+
handlers.onOpenJobsPicker();
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
309
424
|
if (key.escape) {
|
|
310
425
|
if (state.busy) {
|
|
311
426
|
if (state.queuedMessage) {
|
|
@@ -317,10 +432,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
317
432
|
return;
|
|
318
433
|
}
|
|
319
434
|
store.update((current) => {
|
|
320
|
-
if (
|
|
321
|
-
current.cursor === 0 &&
|
|
322
|
-
current.pastedChunks.length === 0 &&
|
|
323
|
-
current.promptHistoryCursor === null) {
|
|
435
|
+
if (composerIsEmpty(current)) {
|
|
324
436
|
return current;
|
|
325
437
|
}
|
|
326
438
|
prepareForComposerInputChange(current, '');
|
|
@@ -340,6 +452,15 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
340
452
|
handlers.onCycleAgentMode();
|
|
341
453
|
return;
|
|
342
454
|
}
|
|
455
|
+
if (state.busy && state.queuedMessage) {
|
|
456
|
+
if (key.returnKey) {
|
|
457
|
+
void handlers.onFireQueuedMessage?.();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (!key.upArrow && !isClipboardImagePasteKey(key)) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
343
464
|
if (commandPaletteActive && (key.upArrow || key.downArrow)) {
|
|
344
465
|
store.update((current) => ({
|
|
345
466
|
...current,
|
|
@@ -348,7 +469,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
348
469
|
return;
|
|
349
470
|
}
|
|
350
471
|
if (key.upArrow) {
|
|
351
|
-
if (state.busy && state.
|
|
472
|
+
if (state.busy && state.queuedMessage) {
|
|
352
473
|
handlers.onLiveFrameShapeChange();
|
|
353
474
|
store.update((current) => {
|
|
354
475
|
const queued = current.queuedMessage;
|
|
@@ -367,9 +488,13 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
367
488
|
});
|
|
368
489
|
return;
|
|
369
490
|
}
|
|
370
|
-
|
|
491
|
+
const nextCursor = moveComposerCursorVertically(state.input, state.cursor, handlers.getComposerInputWidth?.() ?? Number.POSITIVE_INFINITY, -1);
|
|
492
|
+
if (nextCursor === null && state.busy)
|
|
371
493
|
return;
|
|
372
494
|
store.update((current) => {
|
|
495
|
+
if (nextCursor !== null) {
|
|
496
|
+
return { ...current, commandCursor: 0, cursor: nextCursor };
|
|
497
|
+
}
|
|
373
498
|
const next = navigatePromptHistory(current, 'previous');
|
|
374
499
|
prepareForComposerInputChange(current, next.input);
|
|
375
500
|
return next;
|
|
@@ -377,9 +502,13 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
377
502
|
return;
|
|
378
503
|
}
|
|
379
504
|
if (key.downArrow) {
|
|
380
|
-
|
|
505
|
+
const nextCursor = moveComposerCursorVertically(state.input, state.cursor, handlers.getComposerInputWidth?.() ?? Number.POSITIVE_INFINITY, 1);
|
|
506
|
+
if (nextCursor === null && state.busy)
|
|
381
507
|
return;
|
|
382
508
|
store.update((current) => {
|
|
509
|
+
if (nextCursor !== null) {
|
|
510
|
+
return { ...current, commandCursor: 0, cursor: nextCursor };
|
|
511
|
+
}
|
|
383
512
|
const next = navigatePromptHistory(current, 'next');
|
|
384
513
|
prepareForComposerInputChange(current, next.input);
|
|
385
514
|
return next;
|
|
@@ -467,12 +596,16 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
467
596
|
}
|
|
468
597
|
if (isClipboardImagePasteKey(key)) {
|
|
469
598
|
const current = store.getState();
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
599
|
+
const target = current.queuedMessage
|
|
600
|
+
? {
|
|
601
|
+
body: current.queuedMessage.body,
|
|
602
|
+
attachments: current.queuedMessage.imageAttachments,
|
|
603
|
+
}
|
|
604
|
+
: { body: current.input, attachments: current.imageAttachments };
|
|
605
|
+
const liveAttachments = target.attachments.filter((attachment) => target.body.includes(`[Image #${attachment.index}]`));
|
|
606
|
+
if (liveAttachments.length >= MAX_IMAGES_PER_MESSAGE) {
|
|
474
607
|
store.appendEntry({
|
|
475
|
-
body:
|
|
608
|
+
body: `Maximum of ${MAX_IMAGES_PER_MESSAGE} images per message. Send the current message first.`,
|
|
476
609
|
kind: 'error',
|
|
477
610
|
title: 'Image',
|
|
478
611
|
});
|
|
@@ -480,22 +613,49 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
480
613
|
}
|
|
481
614
|
try {
|
|
482
615
|
const clipResult = readClipboardImage();
|
|
616
|
+
if (totalAttachmentBytes(liveAttachments) +
|
|
617
|
+
approximateBase64DecodedBytes(clipResult.base64Data) >
|
|
618
|
+
MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE) {
|
|
619
|
+
store.appendEntry({
|
|
620
|
+
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.`,
|
|
621
|
+
kind: 'error',
|
|
622
|
+
title: 'Image',
|
|
623
|
+
});
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
483
626
|
const idx = liveAttachments.length === 0
|
|
484
627
|
? 1
|
|
485
628
|
: Math.max(...liveAttachments.map((attachment) => attachment.index)) + 1;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
629
|
+
const cached = tryCacheAttachmentBytes({
|
|
630
|
+
base64Data: clipResult.base64Data,
|
|
631
|
+
mimeType: clipResult.mimeType,
|
|
632
|
+
});
|
|
633
|
+
const attachment = {
|
|
634
|
+
index: cached?.index ?? idx,
|
|
635
|
+
mimeType: clipResult.mimeType,
|
|
636
|
+
base64Data: clipResult.base64Data,
|
|
637
|
+
source: 'clipboard',
|
|
638
|
+
...(cached ? { cachePath: cached.cachePath } : {}),
|
|
639
|
+
};
|
|
640
|
+
const marker = attachment.index;
|
|
641
|
+
store.update((shellState) => {
|
|
642
|
+
const nextAttachments = [...liveAttachments, attachment];
|
|
643
|
+
if (shellState.queuedMessage) {
|
|
644
|
+
return {
|
|
645
|
+
...shellState,
|
|
646
|
+
queuedMessage: {
|
|
647
|
+
...shellState.queuedMessage,
|
|
648
|
+
body: `${shellState.queuedMessage.body} [Image #${marker}]`.trim(),
|
|
649
|
+
imageAttachments: nextAttachments,
|
|
650
|
+
},
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
return {
|
|
654
|
+
...shellState,
|
|
655
|
+
imageAttachments: nextAttachments,
|
|
656
|
+
...insertAtCursor(shellState, `[Image #${marker}] `),
|
|
657
|
+
};
|
|
658
|
+
});
|
|
499
659
|
}
|
|
500
660
|
catch (error) {
|
|
501
661
|
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`);
|
|
@@ -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
|
+
}
|