@thegitai/cli 1.0.0-preview.6 → 1.0.0-preview.8
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/help-text.js +3 -2
- package/dist/src/session.js +8 -0
- package/dist/src/ui/repl.js +54 -11
- package/dist/src/ui/tui/build-frame.js +14 -8
- package/dist/src/ui/tui/shell-input.js +42 -13
- package/package.json +5 -5
package/dist/src/help-text.js
CHANGED
|
@@ -57,7 +57,8 @@ const HELP_MARKDOWN = [
|
|
|
57
57
|
'## Keys & clipboard',
|
|
58
58
|
'',
|
|
59
59
|
'- **Enter** sends • **Shift+Tab** cycles modes • **Esc** cancels the turn •',
|
|
60
|
-
' **Ctrl+C**
|
|
60
|
+
' **Ctrl+C** clears the composer or the queued message, and quits once there',
|
|
61
|
+
' is nothing left to clear. These are the same on macOS, Linux, and Windows.',
|
|
61
62
|
`- **Paste** into the composer with your terminal's paste shortcut (\`${PASTE_SHORTCUT}\``,
|
|
62
63
|
' on this system) or by right-clicking the composer.',
|
|
63
64
|
'- **Copy** from the transcript by dragging to select; double-click copies a',
|
|
@@ -78,7 +79,7 @@ const HELP_MARKDOWN = [
|
|
|
78
79
|
' browse them, press Enter to expand one and read its output, k to stop it',
|
|
79
80
|
'- `/jobs output <id>` — print one job\'s full captured output',
|
|
80
81
|
'- `/jobs kill <id>` — stop one background job',
|
|
81
|
-
'- `/
|
|
82
|
+
'- `/new` — start a new conversation; this session remains saved',
|
|
82
83
|
'- `/exit` — quit the session',
|
|
83
84
|
'',
|
|
84
85
|
'## Safety & approvals',
|
package/dist/src/session.js
CHANGED
|
@@ -69,6 +69,14 @@ export function createSession({ rootDir, autoYes = false, agentMode, modelId, ma
|
|
|
69
69
|
serverState: cloneOpaqueState(serverState),
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
export function startNewConversation(session) {
|
|
73
|
+
clearConversation(session);
|
|
74
|
+
const createdAt = new Date().toISOString();
|
|
75
|
+
session.sessionId = createSessionId();
|
|
76
|
+
session.sessionName = null;
|
|
77
|
+
session.sessionCreatedAt = createdAt;
|
|
78
|
+
session.sessionUpdatedAt = createdAt;
|
|
79
|
+
}
|
|
72
80
|
export function clearConversation(session) {
|
|
73
81
|
session.history = [];
|
|
74
82
|
session.serverState = preserveProviderSelection(session.serverState);
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -15,7 +15,7 @@ import { clearCliAuthConfig } from '../api/auth.js';
|
|
|
15
15
|
import { authenticationErrorMessage, isAuthenticationError, } from '../api/http.js';
|
|
16
16
|
import { setCommandOutputHook, withTuiMode } from '../runtime-mode.js';
|
|
17
17
|
import { collectBackgroundJobUiKillMutations, collectBackgroundJobUiOutputMutations, } from '../tool-executor.js';
|
|
18
|
-
import {
|
|
18
|
+
import { startNewConversation, } from '../session.js';
|
|
19
19
|
import { applySessionSnapshot, listSessionMetadata, loadSessionSnapshot, saveSessionState, } from '../session-store.js';
|
|
20
20
|
import { truncate } from '../utils.js';
|
|
21
21
|
import { writeClipboardText } from '../core/clipboard.js';
|
|
@@ -116,8 +116,8 @@ export const SLASH_COMMANDS = [
|
|
|
116
116
|
description: 'Background jobs: pick to view output or kill',
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
|
-
command: '/
|
|
120
|
-
description: '
|
|
119
|
+
command: '/new',
|
|
120
|
+
description: 'start a new conversation; this session remains saved',
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
command: '/exit',
|
|
@@ -1320,6 +1320,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1320
1320
|
let latestUsageSummary = null;
|
|
1321
1321
|
let pendingTurnEntries = [];
|
|
1322
1322
|
let activeTurnAbort = null;
|
|
1323
|
+
let newConversationInFlight = false;
|
|
1323
1324
|
let todosTouchedThisTurn = false;
|
|
1324
1325
|
const syncTodosState = () => {
|
|
1325
1326
|
store.update((current) => ({ ...current, todos: listTodos() }));
|
|
@@ -1521,7 +1522,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1521
1522
|
}));
|
|
1522
1523
|
};
|
|
1523
1524
|
const cancelActiveTurn = () => {
|
|
1524
|
-
if (!store.getState().busy)
|
|
1525
|
+
if (!store.getState().busy || newConversationInFlight)
|
|
1525
1526
|
return;
|
|
1526
1527
|
disarmExitConfirm();
|
|
1527
1528
|
dismissPendingApproval();
|
|
@@ -1677,6 +1678,18 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1677
1678
|
return true;
|
|
1678
1679
|
}
|
|
1679
1680
|
};
|
|
1681
|
+
const saveActiveSessionOrAbort = async () => {
|
|
1682
|
+
try {
|
|
1683
|
+
await saveSessionBoth({ serverSessionClient, session });
|
|
1684
|
+
return true;
|
|
1685
|
+
}
|
|
1686
|
+
catch (error) {
|
|
1687
|
+
if (exitForAuthenticationError(error))
|
|
1688
|
+
return false;
|
|
1689
|
+
appendError(`Session save failed: ${error.message}`);
|
|
1690
|
+
return false;
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1680
1693
|
const openSudoPasswordPrompt = (command, prompt, signal) => new Promise((resolve) => {
|
|
1681
1694
|
if (exiting || signal?.aborted) {
|
|
1682
1695
|
resolve(null);
|
|
@@ -2243,23 +2256,53 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2243
2256
|
}
|
|
2244
2257
|
return;
|
|
2245
2258
|
}
|
|
2246
|
-
if (input === '/
|
|
2247
|
-
|
|
2259
|
+
if (input === '/new') {
|
|
2260
|
+
newConversationInFlight = true;
|
|
2261
|
+
store.update((current) => ({
|
|
2262
|
+
...current,
|
|
2263
|
+
busy: true,
|
|
2264
|
+
busySince: Date.now(),
|
|
2265
|
+
imageAttachments: [],
|
|
2266
|
+
queuedMessage: null,
|
|
2267
|
+
status: 'Starting a new conversation...',
|
|
2268
|
+
}));
|
|
2269
|
+
if (!(await saveActiveSessionOrAbort())) {
|
|
2270
|
+
newConversationInFlight = false;
|
|
2271
|
+
store.update((current) => ({
|
|
2272
|
+
...current,
|
|
2273
|
+
busy: false,
|
|
2274
|
+
busySince: null,
|
|
2275
|
+
status: 'Ready',
|
|
2276
|
+
}));
|
|
2277
|
+
await flushQueuedMessage();
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
startNewConversation(session);
|
|
2281
|
+
setBackgroundJobSession(session.sessionId);
|
|
2282
|
+
setScratchSession(session.sessionId);
|
|
2283
|
+
setTodoSession(session.sessionId);
|
|
2248
2284
|
clearTodos();
|
|
2285
|
+
syncBackgroundJobsState();
|
|
2249
2286
|
syncTodosState();
|
|
2250
2287
|
latestUsageSummary = null;
|
|
2251
|
-
if (!(await saveActiveSession()))
|
|
2252
|
-
return;
|
|
2253
2288
|
store.replaceTranscript([
|
|
2254
2289
|
{
|
|
2255
|
-
body: '
|
|
2290
|
+
body: 'Started a new conversation. The previous session remains saved.',
|
|
2256
2291
|
kind: 'system',
|
|
2257
|
-
title: '
|
|
2292
|
+
title: 'Session',
|
|
2258
2293
|
},
|
|
2259
2294
|
]);
|
|
2295
|
+
await saveActiveSession();
|
|
2260
2296
|
syncShellStateFromSession();
|
|
2261
|
-
|
|
2297
|
+
newConversationInFlight = false;
|
|
2298
|
+
store.update((current) => ({
|
|
2299
|
+
...current,
|
|
2300
|
+
busy: false,
|
|
2301
|
+
busySince: null,
|
|
2302
|
+
status: 'Ready',
|
|
2303
|
+
}));
|
|
2262
2304
|
await remountTui();
|
|
2305
|
+
await flushQueuedMessage();
|
|
2263
2306
|
return;
|
|
2264
2307
|
}
|
|
2265
2308
|
latestUsageSummary = null;
|
|
@@ -163,7 +163,7 @@ const CLIENT_SLASH_COMMANDS = [
|
|
|
163
163
|
{ command: '/model', description: 'Switch the active model' },
|
|
164
164
|
{ command: '/resume', description: 'Open the session picker to resume a previous session' },
|
|
165
165
|
{ command: '/jobs', description: 'Background jobs: pick to view output or kill' },
|
|
166
|
-
{ command: '/
|
|
166
|
+
{ command: '/new', description: 'start a new conversation; this session remains saved' },
|
|
167
167
|
{ command: '/exit', description: 'Quit the current session' },
|
|
168
168
|
];
|
|
169
169
|
function buildModelPickerOptions(currentModelId, serverModels) {
|
|
@@ -405,15 +405,18 @@ function composerFooterLines(state) {
|
|
|
405
405
|
}));
|
|
406
406
|
return lines;
|
|
407
407
|
}
|
|
408
|
+
const busyHelperText = state.queuedMessage
|
|
409
|
+
? 'Enter re-queues • ↑ edit queued • Esc / Ctrl+C clear queued'
|
|
410
|
+
: state.input
|
|
411
|
+
? 'Enter queues • Esc cancels turn • Ctrl+C clears draft'
|
|
412
|
+
: 'Enter queues • Esc / Ctrl+C cancel turn';
|
|
408
413
|
const helperText = state.busy
|
|
409
|
-
?
|
|
410
|
-
? 'Enter re-queues • ↑ edit queued • Esc cancels queued'
|
|
411
|
-
: 'Enter queues • Esc / Ctrl+C cancel turn'
|
|
414
|
+
? busyHelperText
|
|
412
415
|
: process.platform === 'win32'
|
|
413
|
-
? 'Enter sends • Shift+Tab mode • Alt+V image • Esc cancel turn • Ctrl+C quits'
|
|
416
|
+
? 'Enter sends • Shift+Tab mode • Alt+V image • Esc cancel turn • Ctrl+C clears / quits'
|
|
414
417
|
: process.platform === 'darwin'
|
|
415
|
-
? 'Enter sends • Shift+Tab mode • Ctrl+V image • Esc cancel turn • Ctrl+C quits'
|
|
416
|
-
: 'Enter sends • Shift+Tab mode • Ctrl+V image • Esc cancel turn • Ctrl+C quits';
|
|
418
|
+
? 'Enter sends • Shift+Tab mode • Ctrl+V image • Esc cancel turn • Ctrl+C clears / quits'
|
|
419
|
+
: 'Enter sends • Shift+Tab mode • Ctrl+V image • Esc cancel turn • Ctrl+C clears / quits';
|
|
417
420
|
const agentLabel = agentModeLabel(state.agentMode).padEnd(AGENT_MODE_LABEL_WIDTH);
|
|
418
421
|
const tokenUsageText = state.tokenUsage || formatClientTokenUsage(null);
|
|
419
422
|
const footerSpans = [
|
|
@@ -920,7 +923,10 @@ export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, n
|
|
|
920
923
|
const overlayActive = Boolean(state.approvalPrompt || state.sudoPrompt);
|
|
921
924
|
if (!state.resumePickerOpen && !state.modelPickerOpen && !state.jobsPickerOpen && !overlayActive) {
|
|
922
925
|
const composerLines = [];
|
|
923
|
-
if (state.
|
|
926
|
+
if (state.busy && state.status === 'Starting a new conversation...') {
|
|
927
|
+
composerLines.push(line(span('Starting a new conversation…', { color: 'gray', dim: true })));
|
|
928
|
+
}
|
|
929
|
+
else if (state.queuedMessage) {
|
|
924
930
|
const preview = truncate(state.queuedMessage.body.trim().replace(/\s+/g, ' '), 60);
|
|
925
931
|
const imageCount = state.queuedMessage.imageAttachments.length;
|
|
926
932
|
composerLines.push(line(span(`↳ Queued · "${preview}"`, { color: 'gray', dim: true }), ...(imageCount > 0
|
|
@@ -9,6 +9,17 @@ function isClipboardImagePasteKey(key) {
|
|
|
9
9
|
}
|
|
10
10
|
return key.ctrl && key.input === 'v' && !key.shift && !key.meta;
|
|
11
11
|
}
|
|
12
|
+
function composerIsEmpty(state) {
|
|
13
|
+
return (!state.input &&
|
|
14
|
+
state.cursor === 0 &&
|
|
15
|
+
state.pastedChunks.length === 0 &&
|
|
16
|
+
state.promptHistoryCursor === null);
|
|
17
|
+
}
|
|
18
|
+
function composerHasDiscardableDraft(state) {
|
|
19
|
+
if (!composerIsEmpty(state))
|
|
20
|
+
return true;
|
|
21
|
+
return !state.busy && state.imageAttachments.length > 0;
|
|
22
|
+
}
|
|
12
23
|
function shouldShowCommandPalette(state) {
|
|
13
24
|
if (state.busy ||
|
|
14
25
|
state.exiting ||
|
|
@@ -132,11 +143,41 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
132
143
|
return;
|
|
133
144
|
const key = event;
|
|
134
145
|
const state = store.getState();
|
|
146
|
+
const commandPaletteActive = shouldShowCommandPalette(state);
|
|
147
|
+
const commandSuggestions = commandPaletteActive
|
|
148
|
+
? getSlashCommandSuggestions(state.input)
|
|
149
|
+
: [];
|
|
150
|
+
const prepareForComposerInputChange = (current, nextInput) => {
|
|
151
|
+
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
152
|
+
handlers.onLiveFrameShapeChange();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
135
155
|
if (key.ctrl && key.input === 'c') {
|
|
136
156
|
if (state.sudoPrompt) {
|
|
137
157
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
138
158
|
return;
|
|
139
159
|
}
|
|
160
|
+
if (state.queuedMessage) {
|
|
161
|
+
handlers.onLiveFrameShapeChange();
|
|
162
|
+
store.update((current) => ({ ...current, queuedMessage: null }));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (composerHasDiscardableDraft(state)) {
|
|
166
|
+
store.update((current) => {
|
|
167
|
+
prepareForComposerInputChange(current, '');
|
|
168
|
+
return {
|
|
169
|
+
...current,
|
|
170
|
+
commandCursor: 0,
|
|
171
|
+
cursor: 0,
|
|
172
|
+
imageAttachments: current.busy ? current.imageAttachments : [],
|
|
173
|
+
input: '',
|
|
174
|
+
pastedChunks: [],
|
|
175
|
+
promptHistoryCursor: null,
|
|
176
|
+
promptHistoryDraft: '',
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
140
181
|
if (handlers.onCtrlC) {
|
|
141
182
|
handlers.onCtrlC();
|
|
142
183
|
return;
|
|
@@ -144,15 +185,6 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
144
185
|
handlers.onRequestExit();
|
|
145
186
|
return;
|
|
146
187
|
}
|
|
147
|
-
const commandPaletteActive = shouldShowCommandPalette(state);
|
|
148
|
-
const commandSuggestions = commandPaletteActive
|
|
149
|
-
? getSlashCommandSuggestions(state.input)
|
|
150
|
-
: [];
|
|
151
|
-
const prepareForComposerInputChange = (current, nextInput) => {
|
|
152
|
-
if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
|
|
153
|
-
handlers.onLiveFrameShapeChange();
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
188
|
if (state.sudoPrompt) {
|
|
157
189
|
if (key.escape) {
|
|
158
190
|
handlers.onSudoPasswordInput({ kind: 'cancel' });
|
|
@@ -317,10 +349,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
317
349
|
return;
|
|
318
350
|
}
|
|
319
351
|
store.update((current) => {
|
|
320
|
-
if (
|
|
321
|
-
current.cursor === 0 &&
|
|
322
|
-
current.pastedChunks.length === 0 &&
|
|
323
|
-
current.promptHistoryCursor === null) {
|
|
352
|
+
if (composerIsEmpty(current)) {
|
|
324
353
|
return current;
|
|
325
354
|
}
|
|
326
355
|
prepareForComposerInputChange(current, '');
|
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.8",
|
|
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.8",
|
|
41
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.8",
|
|
42
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.8",
|
|
43
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.8",
|
|
44
44
|
"@vscode/ripgrep": "1.18.0"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|