@thegitai/cli 1.0.0-preview.5 → 1.0.0-preview.51
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 +93 -4
- package/dist/bin/ai.js +142 -112
- package/dist/bin/browser-host.js +265 -0
- package/dist/src/agent-mode.js +32 -7
- package/dist/src/api/auth.js +10 -4
- package/dist/src/api/browser-login.js +149 -37
- package/dist/src/api/chat.js +680 -166
- package/dist/src/api/contracts.js +55 -1
- package/dist/src/api/default-host.js +1 -0
- package/dist/src/api/http.js +151 -7
- package/dist/src/api/models.js +29 -10
- package/dist/src/api/startup-handshake.js +36 -0
- package/dist/src/api/turn-recovery.js +189 -0
- package/dist/src/background-jobs.js +4 -2
- package/dist/src/browser/bridge.js +262 -0
- package/dist/src/browser/framing.js +42 -0
- package/dist/src/browser/native-host.js +219 -0
- package/dist/src/browser/protocol.js +46 -0
- package/dist/src/browser/session-bridge.js +130 -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 +112 -21
- package/dist/src/patcher.js +43 -4
- package/dist/src/permissions.js +288 -0
- package/dist/src/preferences.js +39 -0
- package/dist/src/quota-notice.js +35 -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 +13 -46
- package/dist/src/tools/browser.js +641 -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-file.js +5 -0
- 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 +949 -279
- package/dist/src/ui/slash-commands.js +121 -0
- package/dist/src/ui/tui/attention.js +25 -0
- package/dist/src/ui/tui/bridge.js +38 -5
- package/dist/src/ui/tui/build-frame.js +686 -223
- package/dist/src/ui/tui/composer-layout.js +76 -0
- package/dist/src/ui/tui/desktop-notification.js +118 -0
- package/dist/src/ui/tui/markdown-render.js +11 -2
- package/dist/src/ui/tui/shell-input.js +256 -73
- package/dist/src/ui/tui/shortcuts.js +61 -0
- package/dist/src/ui/tui/terminal-theme.js +28 -0
- package/dist/src/ui/tui/user-input.js +593 -0
- package/dist/src/update-check.js +205 -0
- package/dist/src/usage.js +5 -2
- package/dist/src/utils.js +13 -0
- package/dist/src/version.js +52 -2
- package/package.json +22 -9
- package/dist/src/project-index.js +0 -233
- 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,12 @@
|
|
|
1
|
+
import { shortcutsPanelAvailable } from './shortcuts.js';
|
|
2
|
+
import { moveComposerCursorVertically } from './composer-layout.js';
|
|
1
3
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
2
|
-
import {
|
|
4
|
+
import { MAX_IMAGES_PER_MESSAGE, MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE, approximateBase64DecodedBytes, totalAttachmentBytes, } from '../../core/image-limits.js';
|
|
5
|
+
import { tryCacheAttachmentBytes } from '../../core/session-image-store.js';
|
|
6
|
+
import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor, getInputCommandToken, getNextApprovalCursor, getNextModelPickerIndex, getSlashCommandSuggestions, insertAtCursor, navigatePromptHistory, shouldRemountLiveFrameForComposerInputChange, slashCommandMatchesToken, } from '../repl.js';
|
|
3
7
|
import { buildPastePlaceholder, shouldCollapsePaste, } from '../paste-collapse.js';
|
|
8
|
+
import { handleUserInputPromptEvent, } from './user-input.js';
|
|
9
|
+
const APPROVAL_PREVIEW_PAGE_ROWS = 3;
|
|
4
10
|
function isClipboardImagePasteKey(key) {
|
|
5
11
|
if (process.platform === 'win32') {
|
|
6
12
|
return ((key.ctrl || key.meta) &&
|
|
@@ -9,6 +15,38 @@ function isClipboardImagePasteKey(key) {
|
|
|
9
15
|
}
|
|
10
16
|
return key.ctrl && key.input === 'v' && !key.shift && !key.meta;
|
|
11
17
|
}
|
|
18
|
+
export const APPROVAL_INPUT_GUARD_MS = 500;
|
|
19
|
+
function approvalIsGuarded(state) {
|
|
20
|
+
const openedAt = state.approvalOpenedAt;
|
|
21
|
+
if (typeof openedAt !== 'number')
|
|
22
|
+
return false;
|
|
23
|
+
return Date.now() - openedAt < APPROVAL_INPUT_GUARD_MS;
|
|
24
|
+
}
|
|
25
|
+
function applyUserInputPromptEvent(store, handlers, event) {
|
|
26
|
+
const current = store.getState();
|
|
27
|
+
if (!current.userInputPrompt)
|
|
28
|
+
return false;
|
|
29
|
+
const outcome = handleUserInputPromptEvent(current.userInputPrompt, event, handlers.getUserInputViewport?.());
|
|
30
|
+
store.update((state) => ({
|
|
31
|
+
...state,
|
|
32
|
+
userInputPrompt: outcome.state,
|
|
33
|
+
}));
|
|
34
|
+
if (outcome.result) {
|
|
35
|
+
void handlers.onResolveUserInput?.(outcome.result);
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
function composerIsEmpty(state) {
|
|
40
|
+
return (!state.input &&
|
|
41
|
+
state.cursor === 0 &&
|
|
42
|
+
state.pastedChunks.length === 0 &&
|
|
43
|
+
state.promptHistoryCursor === null);
|
|
44
|
+
}
|
|
45
|
+
function composerHasDiscardableDraft(state) {
|
|
46
|
+
if (!composerIsEmpty(state))
|
|
47
|
+
return true;
|
|
48
|
+
return !state.busy && state.imageAttachments.length > 0;
|
|
49
|
+
}
|
|
12
50
|
function shouldShowCommandPalette(state) {
|
|
13
51
|
if (state.busy ||
|
|
14
52
|
state.exiting ||
|
|
@@ -18,7 +56,9 @@ function shouldShowCommandPalette(state) {
|
|
|
18
56
|
return false;
|
|
19
57
|
}
|
|
20
58
|
const token = getInputCommandToken(state.input);
|
|
21
|
-
|
|
59
|
+
if (!token.startsWith('/') || state.input.includes(' '))
|
|
60
|
+
return false;
|
|
61
|
+
return getSlashCommandSuggestions(state.input).length > 0;
|
|
22
62
|
}
|
|
23
63
|
function insertPastedText(store, handlers, text) {
|
|
24
64
|
const current = store.getState();
|
|
@@ -68,12 +108,47 @@ function pasteTextFromClipboard(store, handlers) {
|
|
|
68
108
|
if (!text) {
|
|
69
109
|
store.update((state) => ({
|
|
70
110
|
...state,
|
|
71
|
-
|
|
111
|
+
transientStatus: 'Clipboard has no text to paste',
|
|
72
112
|
}));
|
|
73
113
|
return;
|
|
74
114
|
}
|
|
75
115
|
insertPastedText(store, handlers, text);
|
|
76
116
|
}
|
|
117
|
+
function scrollTranscript(store, handlers, delta) {
|
|
118
|
+
if (delta === 0)
|
|
119
|
+
return;
|
|
120
|
+
store.update((current) => {
|
|
121
|
+
const limit = handlers.getTranscriptScrollLimit?.() ??
|
|
122
|
+
current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
|
|
123
|
+
const next = current.transcriptScrollOffset + delta;
|
|
124
|
+
return {
|
|
125
|
+
...current,
|
|
126
|
+
transcriptScrollOffset: Math.max(0, Math.min(next, limit)),
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function scrollTranscriptTo(store, handlers, offset) {
|
|
131
|
+
store.update((current) => {
|
|
132
|
+
const limit = handlers.getTranscriptScrollLimit?.() ??
|
|
133
|
+
current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
|
|
134
|
+
return {
|
|
135
|
+
...current,
|
|
136
|
+
transcriptScrollOffset: Math.max(0, Math.min(Math.trunc(offset), limit)),
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function scrollApprovalPreview(store, handlers, delta) {
|
|
141
|
+
if (delta === 0)
|
|
142
|
+
return;
|
|
143
|
+
store.update((current) => {
|
|
144
|
+
const limit = handlers.getApprovalScrollLimit?.() ?? 0;
|
|
145
|
+
const next = (current.approvalScrollOffset ?? 0) + delta;
|
|
146
|
+
return {
|
|
147
|
+
...current,
|
|
148
|
+
approvalScrollOffset: Math.max(0, Math.min(next, limit)),
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
77
152
|
function filterResumeSessionsLocal(sessions, filter, serverModels) {
|
|
78
153
|
const q = filter.trim().toLowerCase();
|
|
79
154
|
if (!q)
|
|
@@ -88,6 +163,12 @@ function filterResumeSessionsLocal(sessions, filter, serverModels) {
|
|
|
88
163
|
}
|
|
89
164
|
export function handleShellKeyEvent(store, handlers, event) {
|
|
90
165
|
if (event.kind === 'paste') {
|
|
166
|
+
if (applyUserInputPromptEvent(store, handlers, {
|
|
167
|
+
kind: 'paste',
|
|
168
|
+
text: event.text,
|
|
169
|
+
})) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
91
172
|
insertPastedText(store, handlers, event.text);
|
|
92
173
|
return;
|
|
93
174
|
}
|
|
@@ -110,33 +191,67 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
110
191
|
return;
|
|
111
192
|
}
|
|
112
193
|
if (event.kind === 'contextMenu') {
|
|
194
|
+
if (store.getState().userInputPrompt) {
|
|
195
|
+
const text = (handlers.readClipboardText ?? readClipboardText)();
|
|
196
|
+
if (text) {
|
|
197
|
+
applyUserInputPromptEvent(store, handlers, { kind: 'paste', text });
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
113
201
|
pasteTextFromClipboard(store, handlers);
|
|
114
202
|
return;
|
|
115
203
|
}
|
|
116
204
|
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
|
-
}
|
|
205
|
+
scrollTranscript(store, handlers, Math.trunc(event.deltaLines));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (event.kind === 'transcriptScrollTo') {
|
|
209
|
+
scrollTranscriptTo(store, handlers, Number(event.offset ?? 0));
|
|
129
210
|
return;
|
|
130
211
|
}
|
|
131
212
|
if (event.kind !== 'key')
|
|
132
213
|
return;
|
|
133
214
|
const key = event;
|
|
134
215
|
const state = store.getState();
|
|
216
|
+
const commandPaletteActive = shouldShowCommandPalette(state);
|
|
217
|
+
const commandSuggestions = commandPaletteActive
|
|
218
|
+
? getSlashCommandSuggestions(state.input)
|
|
219
|
+
: [];
|
|
220
|
+
const prepareForComposerInputChange = (current, nextInput) => {
|
|
221
|
+
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
222
|
+
handlers.onLiveFrameShapeChange();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
135
225
|
if (key.ctrl && key.input === 'c') {
|
|
226
|
+
if (state.userInputPrompt) {
|
|
227
|
+
handlers.onCtrlC?.();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
136
230
|
if (state.sudoPrompt) {
|
|
137
231
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
138
232
|
return;
|
|
139
233
|
}
|
|
234
|
+
if (state.queuedMessage) {
|
|
235
|
+
handlers.onLiveFrameShapeChange();
|
|
236
|
+
store.update((current) => ({ ...current, queuedMessage: null }));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (composerHasDiscardableDraft(state)) {
|
|
240
|
+
store.update((current) => {
|
|
241
|
+
prepareForComposerInputChange(current, '');
|
|
242
|
+
return {
|
|
243
|
+
...current,
|
|
244
|
+
commandCursor: 0,
|
|
245
|
+
cursor: 0,
|
|
246
|
+
imageAttachments: current.busy ? current.imageAttachments : [],
|
|
247
|
+
input: '',
|
|
248
|
+
pastedChunks: [],
|
|
249
|
+
promptHistoryCursor: null,
|
|
250
|
+
promptHistoryDraft: '',
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
140
255
|
if (handlers.onCtrlC) {
|
|
141
256
|
handlers.onCtrlC();
|
|
142
257
|
return;
|
|
@@ -144,15 +259,10 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
144
259
|
handlers.onRequestExit();
|
|
145
260
|
return;
|
|
146
261
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const prepareForComposerInputChange = (current, nextInput) => {
|
|
152
|
-
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
153
|
-
handlers.onLiveFrameShapeChange();
|
|
154
|
-
}
|
|
155
|
-
};
|
|
262
|
+
if (state.userInputPrompt &&
|
|
263
|
+
applyUserInputPromptEvent(store, handlers, key)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
156
266
|
if (state.sudoPrompt) {
|
|
157
267
|
if (key.escape) {
|
|
158
268
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
@@ -176,24 +286,32 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
176
286
|
return;
|
|
177
287
|
}
|
|
178
288
|
if (state.approvalPrompt) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
289
|
+
if (key.pageUp || key.pageDown) {
|
|
290
|
+
const delta = key.pageUp ? -APPROVAL_PREVIEW_PAGE_ROWS : APPROVAL_PREVIEW_PAGE_ROWS;
|
|
291
|
+
if (key.shift) {
|
|
292
|
+
scrollTranscript(store, handlers, key.pageUp ? 8 : -8);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
scrollApprovalPreview(store, handlers, delta);
|
|
296
|
+
}
|
|
186
297
|
return;
|
|
187
298
|
}
|
|
188
299
|
if (key.upArrow || key.downArrow) {
|
|
189
300
|
store.update((current) => ({
|
|
190
301
|
...current,
|
|
191
|
-
approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1),
|
|
302
|
+
approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1, current.approvalPrompt?.options?.length ?? 0),
|
|
192
303
|
}));
|
|
193
304
|
return;
|
|
194
305
|
}
|
|
306
|
+
if (approvalIsGuarded(state)) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (key.escape) {
|
|
310
|
+
void handlers.onResolveApproval(-1);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
195
313
|
if (key.returnKey) {
|
|
196
|
-
void handlers.onResolveApproval(
|
|
314
|
+
void handlers.onResolveApproval(state.approvalCursor);
|
|
197
315
|
}
|
|
198
316
|
return;
|
|
199
317
|
}
|
|
@@ -201,24 +319,18 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
201
319
|
return;
|
|
202
320
|
}
|
|
203
321
|
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
|
-
});
|
|
322
|
+
scrollTranscript(store, handlers, key.pageUp ? 8 : -8);
|
|
213
323
|
return;
|
|
214
324
|
}
|
|
215
325
|
if (state.jobsPickerOpen) {
|
|
216
|
-
if (key.escape) {
|
|
326
|
+
if (key.escape || (key.ctrl && key.input === 'b' && !key.meta && !key.shift)) {
|
|
327
|
+
handlers.onLiveFrameShapeChange();
|
|
217
328
|
store.update((current) => ({
|
|
218
329
|
...current,
|
|
219
330
|
jobsPickerExpandedId: null,
|
|
220
331
|
jobsPickerOpen: false,
|
|
221
|
-
|
|
332
|
+
jobsPickerReturnStatus: null,
|
|
333
|
+
status: current.jobsPickerReturnStatus ?? 'Ready',
|
|
222
334
|
}));
|
|
223
335
|
return;
|
|
224
336
|
}
|
|
@@ -306,6 +418,28 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
306
418
|
}
|
|
307
419
|
return;
|
|
308
420
|
}
|
|
421
|
+
if (state.shortcutsOpen) {
|
|
422
|
+
if (key.escape || key.returnKey || key.input === '?' || key.input === 'q') {
|
|
423
|
+
handlers.onLiveFrameShapeChange();
|
|
424
|
+
store.update((current) => ({ ...current, shortcutsOpen: false }));
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (key.ctrl && key.input === 'b' && !key.meta && !key.shift) {
|
|
429
|
+
if ((state.backgroundJobs ?? []).length > 0) {
|
|
430
|
+
handlers.onOpenJobsPicker();
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (key.ctrl && key.input === 'g' && !key.meta && !key.shift) {
|
|
435
|
+
handlers.onToggleNotifications?.();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (key.input === '?' && !key.ctrl && !key.meta && shortcutsPanelAvailable(state)) {
|
|
439
|
+
handlers.onLiveFrameShapeChange();
|
|
440
|
+
store.update((current) => ({ ...current, shortcutsOpen: true }));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
309
443
|
if (key.escape) {
|
|
310
444
|
if (state.busy) {
|
|
311
445
|
if (state.queuedMessage) {
|
|
@@ -317,10 +451,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
317
451
|
return;
|
|
318
452
|
}
|
|
319
453
|
store.update((current) => {
|
|
320
|
-
if (
|
|
321
|
-
current.cursor === 0 &&
|
|
322
|
-
current.pastedChunks.length === 0 &&
|
|
323
|
-
current.promptHistoryCursor === null) {
|
|
454
|
+
if (composerIsEmpty(current)) {
|
|
324
455
|
return current;
|
|
325
456
|
}
|
|
326
457
|
prepareForComposerInputChange(current, '');
|
|
@@ -337,9 +468,22 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
337
468
|
return;
|
|
338
469
|
}
|
|
339
470
|
if (key.tab && key.shift) {
|
|
471
|
+
if (state.busy) {
|
|
472
|
+
handlers.onAgentModeChangeBlocked?.();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
340
475
|
handlers.onCycleAgentMode();
|
|
341
476
|
return;
|
|
342
477
|
}
|
|
478
|
+
if (state.busy && state.queuedMessage) {
|
|
479
|
+
if (key.returnKey) {
|
|
480
|
+
void handlers.onFireQueuedMessage?.();
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (!key.upArrow && !isClipboardImagePasteKey(key)) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
343
487
|
if (commandPaletteActive && (key.upArrow || key.downArrow)) {
|
|
344
488
|
store.update((current) => ({
|
|
345
489
|
...current,
|
|
@@ -348,7 +492,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
348
492
|
return;
|
|
349
493
|
}
|
|
350
494
|
if (key.upArrow) {
|
|
351
|
-
if (state.busy && state.
|
|
495
|
+
if (state.busy && state.queuedMessage) {
|
|
352
496
|
handlers.onLiveFrameShapeChange();
|
|
353
497
|
store.update((current) => {
|
|
354
498
|
const queued = current.queuedMessage;
|
|
@@ -367,9 +511,13 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
367
511
|
});
|
|
368
512
|
return;
|
|
369
513
|
}
|
|
370
|
-
|
|
514
|
+
const nextCursor = moveComposerCursorVertically(state.input, state.cursor, handlers.getComposerInputWidth?.() ?? Number.POSITIVE_INFINITY, -1);
|
|
515
|
+
if (nextCursor === null && state.busy)
|
|
371
516
|
return;
|
|
372
517
|
store.update((current) => {
|
|
518
|
+
if (nextCursor !== null) {
|
|
519
|
+
return { ...current, commandCursor: 0, cursor: nextCursor };
|
|
520
|
+
}
|
|
373
521
|
const next = navigatePromptHistory(current, 'previous');
|
|
374
522
|
prepareForComposerInputChange(current, next.input);
|
|
375
523
|
return next;
|
|
@@ -377,9 +525,13 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
377
525
|
return;
|
|
378
526
|
}
|
|
379
527
|
if (key.downArrow) {
|
|
380
|
-
|
|
528
|
+
const nextCursor = moveComposerCursorVertically(state.input, state.cursor, handlers.getComposerInputWidth?.() ?? Number.POSITIVE_INFINITY, 1);
|
|
529
|
+
if (nextCursor === null && state.busy)
|
|
381
530
|
return;
|
|
382
531
|
store.update((current) => {
|
|
532
|
+
if (nextCursor !== null) {
|
|
533
|
+
return { ...current, commandCursor: 0, cursor: nextCursor };
|
|
534
|
+
}
|
|
383
535
|
const next = navigatePromptHistory(current, 'next');
|
|
384
536
|
prepareForComposerInputChange(current, next.input);
|
|
385
537
|
return next;
|
|
@@ -399,10 +551,10 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
399
551
|
}
|
|
400
552
|
if (key.returnKey) {
|
|
401
553
|
const token = getInputCommandToken(state.input);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
554
|
+
const suggestion = commandSuggestions[Math.min(state.commandCursor, commandSuggestions.length - 1)];
|
|
555
|
+
if (commandPaletteActive &&
|
|
556
|
+
suggestion &&
|
|
557
|
+
!slashCommandMatchesToken(suggestion, token)) {
|
|
406
558
|
store.update((current) => {
|
|
407
559
|
const nextInput = applySlashCommandSuggestion(current.input, suggestion);
|
|
408
560
|
prepareForComposerInputChange(current, nextInput.input);
|
|
@@ -467,12 +619,16 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
467
619
|
}
|
|
468
620
|
if (isClipboardImagePasteKey(key)) {
|
|
469
621
|
const current = store.getState();
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
622
|
+
const target = current.queuedMessage
|
|
623
|
+
? {
|
|
624
|
+
body: current.queuedMessage.body,
|
|
625
|
+
attachments: current.queuedMessage.imageAttachments,
|
|
626
|
+
}
|
|
627
|
+
: { body: current.input, attachments: current.imageAttachments };
|
|
628
|
+
const liveAttachments = target.attachments.filter((attachment) => target.body.includes(`[Image #${attachment.index}]`));
|
|
629
|
+
if (liveAttachments.length >= MAX_IMAGES_PER_MESSAGE) {
|
|
474
630
|
store.appendEntry({
|
|
475
|
-
body:
|
|
631
|
+
body: `Maximum of ${MAX_IMAGES_PER_MESSAGE} images per message. Send the current message first.`,
|
|
476
632
|
kind: 'error',
|
|
477
633
|
title: 'Image',
|
|
478
634
|
});
|
|
@@ -480,22 +636,49 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
480
636
|
}
|
|
481
637
|
try {
|
|
482
638
|
const clipResult = readClipboardImage();
|
|
639
|
+
if (totalAttachmentBytes(liveAttachments) +
|
|
640
|
+
approximateBase64DecodedBytes(clipResult.base64Data) >
|
|
641
|
+
MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE) {
|
|
642
|
+
store.appendEntry({
|
|
643
|
+
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.`,
|
|
644
|
+
kind: 'error',
|
|
645
|
+
title: 'Image',
|
|
646
|
+
});
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
483
649
|
const idx = liveAttachments.length === 0
|
|
484
650
|
? 1
|
|
485
651
|
: Math.max(...liveAttachments.map((attachment) => attachment.index)) + 1;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
652
|
+
const cached = tryCacheAttachmentBytes({
|
|
653
|
+
base64Data: clipResult.base64Data,
|
|
654
|
+
mimeType: clipResult.mimeType,
|
|
655
|
+
});
|
|
656
|
+
const attachment = {
|
|
657
|
+
index: cached?.index ?? idx,
|
|
658
|
+
mimeType: clipResult.mimeType,
|
|
659
|
+
base64Data: clipResult.base64Data,
|
|
660
|
+
source: 'clipboard',
|
|
661
|
+
...(cached ? { cachePath: cached.cachePath } : {}),
|
|
662
|
+
};
|
|
663
|
+
const marker = attachment.index;
|
|
664
|
+
store.update((shellState) => {
|
|
665
|
+
const nextAttachments = [...liveAttachments, attachment];
|
|
666
|
+
if (shellState.queuedMessage) {
|
|
667
|
+
return {
|
|
668
|
+
...shellState,
|
|
669
|
+
queuedMessage: {
|
|
670
|
+
...shellState.queuedMessage,
|
|
671
|
+
body: `${shellState.queuedMessage.body} [Image #${marker}]`.trim(),
|
|
672
|
+
imageAttachments: nextAttachments,
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
...shellState,
|
|
678
|
+
imageAttachments: nextAttachments,
|
|
679
|
+
...insertAtCursor(shellState, `[Image #${marker}] `),
|
|
680
|
+
};
|
|
681
|
+
});
|
|
499
682
|
}
|
|
500
683
|
catch (error) {
|
|
501
684
|
if (error?.code === 'NO_IMAGE') {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { agentModeCycleSequence } from '../../agent-mode.js';
|
|
2
|
+
export function shortcutsPanelAvailable(state) {
|
|
3
|
+
return (!state.exiting &&
|
|
4
|
+
!state.approvalPrompt &&
|
|
5
|
+
!state.sudoPrompt &&
|
|
6
|
+
!state.userInputPrompt &&
|
|
7
|
+
!state.queuedMessage &&
|
|
8
|
+
!state.input &&
|
|
9
|
+
(state.cursor ?? 0) === 0 &&
|
|
10
|
+
(state.pastedChunks?.length ?? 0) === 0 &&
|
|
11
|
+
state.promptHistoryCursor == null);
|
|
12
|
+
}
|
|
13
|
+
export function buildShortcutGroups(platform = process.platform) {
|
|
14
|
+
const imagePaste = platform === 'win32' ? 'Alt+V' : 'Ctrl+V';
|
|
15
|
+
const textPaste = platform === 'darwin' ? 'Cmd+V' : 'Ctrl+Shift+V';
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
title: 'Composer',
|
|
19
|
+
rows: [
|
|
20
|
+
{ keys: 'Enter', description: 'Send your message' },
|
|
21
|
+
{
|
|
22
|
+
keys: 'Shift+Tab',
|
|
23
|
+
description: `Cycle ${agentModeCycleSequence()} (between turns)`,
|
|
24
|
+
},
|
|
25
|
+
{ keys: '/', description: 'Slash commands — /help lists them all' },
|
|
26
|
+
{ keys: '↑ / ↓', description: 'Earlier prompts' },
|
|
27
|
+
{ keys: 'Esc', description: 'Clear the composer' },
|
|
28
|
+
{ keys: 'Ctrl+C', description: 'Clear the composer, or quit when empty' },
|
|
29
|
+
{ keys: 'Ctrl+G', description: 'Desktop notifications — toggle on or off' },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
title: 'While the agent is working',
|
|
34
|
+
rows: [
|
|
35
|
+
{ keys: 'Enter', description: 'Queue a message; Enter again sends it into the turn' },
|
|
36
|
+
{ keys: '↑', description: 'Bring a queued message back to edit it' },
|
|
37
|
+
{ keys: 'Esc', description: 'Discard the queued message, or cancel the turn' },
|
|
38
|
+
{ keys: 'Ctrl+C', description: 'Cancel the turn' },
|
|
39
|
+
{ keys: 'Ctrl+B', description: 'Background jobs' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: 'Clipboard & images',
|
|
44
|
+
rows: [
|
|
45
|
+
{ keys: imagePaste, description: 'Paste a screenshot into the message' },
|
|
46
|
+
{ keys: textPaste, description: 'Paste text (your terminal’s own shortcut)' },
|
|
47
|
+
{ keys: 'Right-click', description: 'Paste, or copy the link under the cursor' },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
title: 'Transcript',
|
|
52
|
+
rows: [
|
|
53
|
+
{ keys: 'PageUp / PageDown', description: 'Scroll — the mouse wheel works too' },
|
|
54
|
+
{ keys: 'Drag', description: 'Select and copy' },
|
|
55
|
+
{ keys: 'Double-click', description: 'Copy a word' },
|
|
56
|
+
{ keys: 'Triple-click', description: 'Copy a whole row' },
|
|
57
|
+
{ keys: 'Left-click', description: 'Open a link' },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
@@ -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
|
+
}
|