@thegitai/cli 1.0.0-preview.15 → 1.0.0-preview.17
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/agent-mode.js +1 -1
- package/dist/src/api/chat.js +34 -2
- package/dist/src/help-text.js +32 -4
- package/dist/src/permissions.js +243 -0
- package/dist/src/session-store.js +5 -0
- package/dist/src/session.js +5 -3
- package/dist/src/tool-executor.js +2 -2
- package/dist/src/tools/delete-file.js +14 -0
- package/dist/src/tools/patch-file.js +12 -16
- package/dist/src/tools/replace-document-text.js +28 -18
- package/dist/src/tools/run-command.js +13 -27
- package/dist/src/tools/run-node-script.js +11 -26
- package/dist/src/tools/str-replace.js +12 -16
- package/dist/src/tools/write-file.js +66 -0
- package/dist/src/ui/repl.js +206 -85
- package/dist/src/ui/tui/build-frame.js +54 -16
- package/dist/src/ui/tui/shell-input.js +27 -13
- package/package.json +5 -5
|
@@ -2,7 +2,7 @@ import { agentModeLabel } from '../../agent-mode.js';
|
|
|
2
2
|
import { singleLinePreview, truncate } from '../../utils.js';
|
|
3
3
|
import { formatClientTokenUsage } from '../repl.js';
|
|
4
4
|
import { renderFormattedBodyLines, renderPreformattedBodyLines, } from './markdown-render.js';
|
|
5
|
-
import { displayWidth, line, plainLine, sliceToWidth, span, wrapText, } from './text.js';
|
|
5
|
+
import { displayWidth, line, padToWidth, plainLine, sliceToWidth, span, wrapText, } from './text.js';
|
|
6
6
|
import { buildUserInputOverlayLines, } from './user-input.js';
|
|
7
7
|
const WORKING_CLOCK_ICON = '◷';
|
|
8
8
|
const BRAILLE_SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴'];
|
|
@@ -286,6 +286,46 @@ function todoItemLine(item, width, progressTick = null) {
|
|
|
286
286
|
}
|
|
287
287
|
return line(span(' ○ ', { color: 'gray' }), span(text));
|
|
288
288
|
}
|
|
289
|
+
const TURN_MESSAGE_PANEL_MAX_ROWS = 6;
|
|
290
|
+
const TURN_MESSAGE_LABEL = {
|
|
291
|
+
queued: 'queued',
|
|
292
|
+
sending: 'Processing . . .',
|
|
293
|
+
delivered: 'delivered',
|
|
294
|
+
};
|
|
295
|
+
function turnMessageLine(message, width, progressTick) {
|
|
296
|
+
const DECORATION = 6;
|
|
297
|
+
const budget = Math.max(1, width - DECORATION);
|
|
298
|
+
const preferredLabel = Math.max(...Object.values(TURN_MESSAGE_LABEL).map((value) => displayWidth(value)));
|
|
299
|
+
const labelWidth = Math.min(Math.max(1, Math.floor(budget / 2)), Math.max(preferredLabel, displayWidth(message.note ?? '')));
|
|
300
|
+
const label = fitLine(message.note ?? TURN_MESSAGE_LABEL[message.state], labelWidth);
|
|
301
|
+
const textWidth = Math.max(1, budget - labelWidth);
|
|
302
|
+
const text = padToWidth(fitLine(`"${message.text}"`, textWidth), textWidth);
|
|
303
|
+
if (message.state === 'delivered') {
|
|
304
|
+
return line(span(' ● ', { color: 'green' }), span(text, { color: 'gray', dim: true }), span(` ${label}`, { color: 'green' }));
|
|
305
|
+
}
|
|
306
|
+
if (message.state === 'sending') {
|
|
307
|
+
return line(span(` ${todoInProgressGlyph(progressTick)} `, {
|
|
308
|
+
color: TODO_IN_PROGRESS_COLOR,
|
|
309
|
+
}), span(text, { color: TODO_IN_PROGRESS_COLOR, bold: true }), span(` ${label}`, { color: TODO_IN_PROGRESS_COLOR }));
|
|
310
|
+
}
|
|
311
|
+
return line(span(' ○ ', { color: 'cyan' }), span(text, { color: 'cyan' }), span(` ${label}`, { color: 'gray' }));
|
|
312
|
+
}
|
|
313
|
+
export function buildTurnMessageLines(messages, width, progressTick = null) {
|
|
314
|
+
if (messages.length === 0)
|
|
315
|
+
return [];
|
|
316
|
+
const lines = [
|
|
317
|
+
line(span('Your messages', { color: 'cyan', bold: true })),
|
|
318
|
+
];
|
|
319
|
+
const visible = messages.slice(-(TURN_MESSAGE_PANEL_MAX_ROWS - 1));
|
|
320
|
+
const hidden = messages.length - visible.length;
|
|
321
|
+
if (hidden > 0) {
|
|
322
|
+
lines.push(line(span(` ● ${hidden} earlier`, { color: 'gray', dim: true })));
|
|
323
|
+
}
|
|
324
|
+
for (const message of visible) {
|
|
325
|
+
lines.push(turnMessageLine(message, width, progressTick));
|
|
326
|
+
}
|
|
327
|
+
return lines;
|
|
328
|
+
}
|
|
289
329
|
export function formatTodoProgress(items) {
|
|
290
330
|
const done = items.filter((item) => item.status === 'completed').length;
|
|
291
331
|
return `${done}/${items.length} done`;
|
|
@@ -472,7 +512,7 @@ function composerFooterLines(state) {
|
|
|
472
512
|
return lines;
|
|
473
513
|
}
|
|
474
514
|
const busyHelperText = state.queuedMessage
|
|
475
|
-
? 'Enter
|
|
515
|
+
? 'Enter sends it to the agent • ↑ edit • Esc / Ctrl+C discard'
|
|
476
516
|
: state.input
|
|
477
517
|
? 'Enter queues • Esc cancels turn • Ctrl+C clears draft'
|
|
478
518
|
: 'Enter queues • Esc / Ctrl+C cancel turn';
|
|
@@ -649,6 +689,11 @@ function buildLiveLines(state, width, spinnerFrame, elapsedSeconds, nowMs) {
|
|
|
649
689
|
lines.push(plainLine(''));
|
|
650
690
|
}
|
|
651
691
|
lines.push(plainLine(buildWorkingClockLine(state, elapsedSeconds), { color: 'yellow' }));
|
|
692
|
+
const turnMessages = state.turnMessages ?? [];
|
|
693
|
+
if (turnMessages.length > 0) {
|
|
694
|
+
lines.push(plainLine(''));
|
|
695
|
+
lines.push(...buildTurnMessageLines(turnMessages, width, todoProgressTick(nowMs)));
|
|
696
|
+
}
|
|
652
697
|
const todos = state.todos ?? [];
|
|
653
698
|
if (todos.length > 0) {
|
|
654
699
|
lines.push(plainLine(''));
|
|
@@ -1055,25 +1100,19 @@ function buildOverlayLines(state, width, height, nowMs) {
|
|
|
1055
1100
|
if (padded)
|
|
1056
1101
|
lines.push(plainLine(''));
|
|
1057
1102
|
}
|
|
1058
|
-
const options = [
|
|
1059
|
-
|
|
1060
|
-
{ value: 'a', label: 'Approve all remaining actions' },
|
|
1061
|
-
{ value: 'n', label: 'Deny' },
|
|
1062
|
-
];
|
|
1063
|
-
options.forEach((option, index) => {
|
|
1103
|
+
const options = (prompt.options ?? []).map((option) => option.label);
|
|
1104
|
+
options.forEach((label, index) => {
|
|
1064
1105
|
const selected = index === state.approvalCursor;
|
|
1065
1106
|
lines.push(line(span(selected ? '› ' : ' ', {
|
|
1066
1107
|
color: selected ? APPROVAL_ACCENT_COLOR : 'gray',
|
|
1067
|
-
}), span(
|
|
1108
|
+
}), span(label, {
|
|
1068
1109
|
color: selected ? APPROVAL_ACCENT_COLOR : undefined,
|
|
1069
1110
|
bold: selected,
|
|
1070
|
-
}), span(` ${option.label}`, {
|
|
1071
|
-
color: selected ? APPROVAL_ACCENT_COLOR : undefined,
|
|
1072
1111
|
})));
|
|
1073
1112
|
});
|
|
1074
1113
|
if (padded)
|
|
1075
1114
|
lines.push(plainLine(''));
|
|
1076
|
-
lines.push(plainLine('
|
|
1115
|
+
lines.push(plainLine('↑/↓ moves • Enter confirms • Esc denies', {
|
|
1077
1116
|
color: 'gray',
|
|
1078
1117
|
}));
|
|
1079
1118
|
return buildOverlayPanel(lines, width, OVERLAY_BORDER_COLOR, height);
|
|
@@ -1198,11 +1237,10 @@ export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, n
|
|
|
1198
1237
|
composerLines.push(line(span('Starting a new conversation…', { color: 'gray', dim: true })));
|
|
1199
1238
|
}
|
|
1200
1239
|
else if (state.queuedMessage) {
|
|
1201
|
-
const preview = truncate(state.queuedMessage.body.trim().replace(/\s+/g, ' '), 60);
|
|
1202
1240
|
const imageCount = state.queuedMessage.imageAttachments.length;
|
|
1203
|
-
composerLines.push(line(span(
|
|
1204
|
-
?
|
|
1205
|
-
:
|
|
1241
|
+
composerLines.push(line(span('Queued', { color: 'cyan', bold: true }), span(' Enter', { color: 'cyan', bold: true }), span(imageCount > 0
|
|
1242
|
+
? ' sends it with the next prompt'
|
|
1243
|
+
: ' 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' })));
|
|
1206
1244
|
}
|
|
1207
1245
|
else {
|
|
1208
1246
|
const promptLabel = state.busy ? 'queue> ' : '❯ ';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
2
|
-
import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor,
|
|
2
|
+
import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor, getInputCommandToken, getNextApprovalCursor, getNextModelPickerIndex, getSlashCommandSuggestions, insertAtCursor, isExactSlashCommandToken, navigatePromptHistory, shouldRemountLiveFrameForComposerInputChange, } from '../repl.js';
|
|
3
3
|
import { buildPastePlaceholder, shouldCollapsePaste, } from '../paste-collapse.js';
|
|
4
4
|
import { handleUserInputPromptEvent, } from './user-input.js';
|
|
5
5
|
const APPROVAL_PREVIEW_PAGE_ROWS = 3;
|
|
@@ -11,6 +11,13 @@ function isClipboardImagePasteKey(key) {
|
|
|
11
11
|
}
|
|
12
12
|
return key.ctrl && key.input === 'v' && !key.shift && !key.meta;
|
|
13
13
|
}
|
|
14
|
+
export const APPROVAL_INPUT_GUARD_MS = 500;
|
|
15
|
+
function approvalIsGuarded(state) {
|
|
16
|
+
const openedAt = state.approvalOpenedAt;
|
|
17
|
+
if (typeof openedAt !== 'number')
|
|
18
|
+
return false;
|
|
19
|
+
return Date.now() - openedAt < APPROVAL_INPUT_GUARD_MS;
|
|
20
|
+
}
|
|
14
21
|
function applyUserInputPromptEvent(store, handlers, event) {
|
|
15
22
|
const current = store.getState();
|
|
16
23
|
if (!current.userInputPrompt)
|
|
@@ -283,24 +290,22 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
283
290
|
}
|
|
284
291
|
return;
|
|
285
292
|
}
|
|
286
|
-
const directChoice = key.ctrl || key.meta ? null : resolveApprovalChoiceFromInput(key.input);
|
|
287
|
-
if (directChoice) {
|
|
288
|
-
void handlers.onResolveApproval(directChoice);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (key.escape) {
|
|
292
|
-
void handlers.onResolveApproval('n');
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
293
|
if (key.upArrow || key.downArrow) {
|
|
296
294
|
store.update((current) => ({
|
|
297
295
|
...current,
|
|
298
|
-
approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1),
|
|
296
|
+
approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1, current.approvalPrompt?.options?.length ?? 0),
|
|
299
297
|
}));
|
|
300
298
|
return;
|
|
301
299
|
}
|
|
300
|
+
if (approvalIsGuarded(state)) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (key.escape) {
|
|
304
|
+
void handlers.onResolveApproval(-1);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
302
307
|
if (key.returnKey) {
|
|
303
|
-
void handlers.onResolveApproval(
|
|
308
|
+
void handlers.onResolveApproval(state.approvalCursor);
|
|
304
309
|
}
|
|
305
310
|
return;
|
|
306
311
|
}
|
|
@@ -436,6 +441,15 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
436
441
|
handlers.onCycleAgentMode();
|
|
437
442
|
return;
|
|
438
443
|
}
|
|
444
|
+
if (state.busy && state.queuedMessage) {
|
|
445
|
+
if (key.returnKey) {
|
|
446
|
+
void handlers.onFireQueuedMessage?.();
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (!key.upArrow) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
439
453
|
if (commandPaletteActive && (key.upArrow || key.downArrow)) {
|
|
440
454
|
store.update((current) => ({
|
|
441
455
|
...current,
|
|
@@ -444,7 +458,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
444
458
|
return;
|
|
445
459
|
}
|
|
446
460
|
if (key.upArrow) {
|
|
447
|
-
if (state.busy && state.
|
|
461
|
+
if (state.busy && state.queuedMessage) {
|
|
448
462
|
handlers.onLiveFrameShapeChange();
|
|
449
463
|
store.update((current) => {
|
|
450
464
|
const queued = current.queuedMessage;
|
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.17",
|
|
4
4
|
"description": "TheGitAI is an AI coding agent for your terminal. It indexes your repository, writes and edits files, runs commands, and builds features with you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
38
38
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
39
39
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
40
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
41
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
42
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
43
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
40
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.17",
|
|
41
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.17",
|
|
42
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.17",
|
|
43
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.17",
|
|
44
44
|
"@vscode/ripgrep": "1.18.0"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|