@thegitai/cli 1.0.0-preview.5 → 1.0.0-preview.7
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 +4 -0
- package/dist/bin/ai.js +8 -2
- package/dist/src/api/auth.js +2 -2
- package/dist/src/api/browser-login.js +2 -2
- package/dist/src/api/http.js +16 -3
- package/dist/src/api/models.js +2 -2
- package/dist/src/help-text.js +2 -1
- package/dist/src/ui/repl.js +67 -27
- package/dist/src/ui/tui/build-frame.js +9 -6
- package/dist/src/ui/tui/shell-input.js +42 -13
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -34,6 +34,10 @@ always be viewed or resumed from that computer. Continuing it through the
|
|
|
34
34
|
service requires the TheGitAI account used for that session. Local sessions can
|
|
35
35
|
also be listed while signed out or offline.
|
|
36
36
|
|
|
37
|
+
CLI login tokens use a rolling 24-hour inactivity timeout. If one expires during
|
|
38
|
+
any server request, the CLI saves the local session, removes the expired
|
|
39
|
+
credential, and asks you to run `ai login` before resuming.
|
|
40
|
+
|
|
37
41
|
## Visible to-do list
|
|
38
42
|
|
|
39
43
|
For larger multi-step tasks, the agent keeps a compact to-do list on screen so
|
package/dist/bin/ai.js
CHANGED
|
@@ -4,7 +4,7 @@ import { stdin as input, stdout as output } from 'node:process';
|
|
|
4
4
|
import readline from 'node:readline/promises';
|
|
5
5
|
import { ServerApi } from '../src/api/index.js';
|
|
6
6
|
import { loginViaBrowser } from '../src/api/browser-login.js';
|
|
7
|
-
import { isTransientNetworkError } from '../src/api/http.js';
|
|
7
|
+
import { authenticationErrorMessage, isAuthenticationError, isTransientNetworkError, } from '../src/api/http.js';
|
|
8
8
|
import { formatCliHelpText } from '../src/help-text.js';
|
|
9
9
|
import { createIndex } from '../src/project-index.js';
|
|
10
10
|
import { createSession } from '../src/session.js';
|
|
@@ -268,6 +268,12 @@ export async function main() {
|
|
|
268
268
|
printSessionExit(session);
|
|
269
269
|
}
|
|
270
270
|
main().catch((error) => {
|
|
271
|
-
|
|
271
|
+
if (isAuthenticationError(error)) {
|
|
272
|
+
auth.clearCliAuthConfig();
|
|
273
|
+
console.error(chalk.red(`\n✖ Error: ${authenticationErrorMessage(error)}\n`));
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
console.error(chalk.red(`\n✖ Error: ${error.message}\n`));
|
|
277
|
+
}
|
|
272
278
|
process.exit(1);
|
|
273
279
|
});
|
package/dist/src/api/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getClientStateDir } from '../client-state.js';
|
|
4
|
-
import { ServerApiError, authorizedJson, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, retryTransient, } from './http.js';
|
|
4
|
+
import { ServerApiError, authorizedJson, createTraceContext, failureCode, failureMessage, normalizeServerUrl, readJsonResponse, retryTransient, } from './http.js';
|
|
5
5
|
export function getAuthConfigPath(env = process.env) {
|
|
6
6
|
const configured = String(env.THEGITAI_AUTH_CONFIG ?? '').trim();
|
|
7
7
|
if (configured) {
|
|
@@ -78,6 +78,6 @@ export async function logoutFromServer({ config, fetchImpl = globalThis.fetch, }
|
|
|
78
78
|
});
|
|
79
79
|
if (!response.ok && response.status !== 401) {
|
|
80
80
|
const data = (await readJsonResponse(response));
|
|
81
|
-
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
|
|
81
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId, failureCode(data));
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -2,7 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import http from 'node:http';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import { openUrl } from '../core/open-url.js';
|
|
5
|
-
import { ServerApiError, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
|
|
5
|
+
import { ServerApiError, createTraceContext, failureCode, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
|
|
6
6
|
const DEFAULT_WEBSITE_URL = 'https://thegit.ai';
|
|
7
7
|
const DEFAULT_SERVER_URL = 'https://thegit.ai';
|
|
8
8
|
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
@@ -56,7 +56,7 @@ async function exchangeCodeForToken({ serverUrl, code, codeVerifier, fetchImpl,
|
|
|
56
56
|
});
|
|
57
57
|
const data = (await readJsonResponse(response));
|
|
58
58
|
if (!response.ok) {
|
|
59
|
-
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
|
|
59
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId, failureCode(data));
|
|
60
60
|
}
|
|
61
61
|
const token = String(data?.token ?? '').trim();
|
|
62
62
|
const customer = data?.customer;
|
package/dist/src/api/http.js
CHANGED
|
@@ -6,11 +6,13 @@ export const CLIENT_PLATFORM_HEADER = 'x-thegitai-client-platform';
|
|
|
6
6
|
export class ServerApiError extends Error {
|
|
7
7
|
status;
|
|
8
8
|
traceId;
|
|
9
|
-
|
|
9
|
+
code;
|
|
10
|
+
constructor(message, status, traceId, code = '') {
|
|
10
11
|
super(traceId ? `${message}\nTrace ID: ${traceId}` : message);
|
|
11
12
|
this.name = 'ServerApiError';
|
|
12
13
|
this.status = status;
|
|
13
14
|
this.traceId = traceId;
|
|
15
|
+
this.code = code;
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
export function createTraceId() {
|
|
@@ -96,9 +98,20 @@ export async function readJsonResponse(response) {
|
|
|
96
98
|
export function failureMessage(data, status) {
|
|
97
99
|
return String(data?.error?.message ?? data?.message ?? `Request failed with ${status}`);
|
|
98
100
|
}
|
|
101
|
+
export function failureCode(data) {
|
|
102
|
+
return typeof data?.error?.code === 'string' ? data.error.code : '';
|
|
103
|
+
}
|
|
104
|
+
export function isAuthenticationError(error) {
|
|
105
|
+
return error instanceof ServerApiError && error.status === 401;
|
|
106
|
+
}
|
|
107
|
+
export function authenticationErrorMessage(error) {
|
|
108
|
+
return error.code === 'AUTH_TOKEN_EXPIRED'
|
|
109
|
+
? 'Your login expired after 24 hours of inactivity. Run `ai login` and resume this saved session.'
|
|
110
|
+
: 'Your login is no longer valid. Run `ai login` and resume this saved session.';
|
|
111
|
+
}
|
|
99
112
|
export async function readErrorResponse(response, traceId = response.headers.get(TRACE_ID_HEADER) ?? '') {
|
|
100
113
|
const data = await readJsonResponse(response);
|
|
101
|
-
return new ServerApiError(failureMessage(data, response.status), response.status, traceId);
|
|
114
|
+
return new ServerApiError(failureMessage(data, response.status), response.status, traceId, failureCode(data));
|
|
102
115
|
}
|
|
103
116
|
export async function authorizedJson({ config, path, method = 'GET', body = null, headers = {}, fetchImpl = globalThis.fetch, timeoutMs = REQUEST_TIMEOUT_MS, }) {
|
|
104
117
|
const trace = createTraceContext();
|
|
@@ -115,7 +128,7 @@ export async function authorizedJson({ config, path, method = 'GET', body = null
|
|
|
115
128
|
});
|
|
116
129
|
const data = await readJsonResponse(response);
|
|
117
130
|
if (!response.ok) {
|
|
118
|
-
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
|
|
131
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId, failureCode(data));
|
|
119
132
|
}
|
|
120
133
|
return data;
|
|
121
134
|
}
|
package/dist/src/api/models.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getClientStateDir } from '../client-state.js';
|
|
4
|
-
import { REQUEST_TIMEOUT_MS, ServerApiError, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, retryTransient, } from './http.js';
|
|
4
|
+
import { REQUEST_TIMEOUT_MS, ServerApiError, createTraceContext, failureCode, failureMessage, normalizeServerUrl, readJsonResponse, retryTransient, } from './http.js';
|
|
5
5
|
function sanitizeModelInfo(raw) {
|
|
6
6
|
if (!raw || typeof raw !== 'object') {
|
|
7
7
|
return null;
|
|
@@ -75,7 +75,7 @@ export async function fetchServerModels({ config, fetchImpl = globalThis.fetch,
|
|
|
75
75
|
});
|
|
76
76
|
const data = (await readJsonResponse(response));
|
|
77
77
|
if (!response.ok) {
|
|
78
|
-
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
|
|
78
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId, failureCode(data));
|
|
79
79
|
}
|
|
80
80
|
const models = Array.isArray(data?.models)
|
|
81
81
|
? data.models.map(sanitizeModelInfo).filter(Boolean)
|
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',
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -11,6 +11,8 @@ import { clearTodos, listTodos, setTodoSession } from '../todo-list.js';
|
|
|
11
11
|
import { setScratchSession } from '../scratch-dir.js';
|
|
12
12
|
import { cancelActiveCommand } from '../executor.js';
|
|
13
13
|
import { isTurnFailureMarker } from '../turn-failure-marker.js';
|
|
14
|
+
import { clearCliAuthConfig } from '../api/auth.js';
|
|
15
|
+
import { authenticationErrorMessage, isAuthenticationError, } from '../api/http.js';
|
|
14
16
|
import { setCommandOutputHook, withTuiMode } from '../runtime-mode.js';
|
|
15
17
|
import { collectBackgroundJobUiKillMutations, collectBackgroundJobUiOutputMutations, } from '../tool-executor.js';
|
|
16
18
|
import { clearConversation, } from '../session.js';
|
|
@@ -1290,6 +1292,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1290
1292
|
if (process.stdout.isTTY !== true) {
|
|
1291
1293
|
throw new Error('stdout is not a terminal');
|
|
1292
1294
|
}
|
|
1295
|
+
let fatalError = null;
|
|
1293
1296
|
await withTuiMode(async () => {
|
|
1294
1297
|
setBackgroundJobSession(session.sessionId);
|
|
1295
1298
|
setScratchSession(session.sessionId);
|
|
@@ -1562,14 +1565,6 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1562
1565
|
scheduleLiveFrameRemount();
|
|
1563
1566
|
void remountTui();
|
|
1564
1567
|
};
|
|
1565
|
-
const saveActiveSession = async () => {
|
|
1566
|
-
try {
|
|
1567
|
-
await saveSessionBoth({ serverSessionClient, session });
|
|
1568
|
-
}
|
|
1569
|
-
catch (error) {
|
|
1570
|
-
appendError(`Session save failed: ${error.message}`);
|
|
1571
|
-
}
|
|
1572
|
-
};
|
|
1573
1568
|
const syncShellStateFromSession = () => {
|
|
1574
1569
|
store.update((current) => ({
|
|
1575
1570
|
...current,
|
|
@@ -1661,6 +1656,27 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1661
1656
|
resolveDone?.();
|
|
1662
1657
|
});
|
|
1663
1658
|
};
|
|
1659
|
+
const exitForAuthenticationError = (error) => {
|
|
1660
|
+
if (!isAuthenticationError(error))
|
|
1661
|
+
return false;
|
|
1662
|
+
clearCliAuthConfig(session.env);
|
|
1663
|
+
saveSessionState(session);
|
|
1664
|
+
fatalError = new Error(authenticationErrorMessage(error));
|
|
1665
|
+
requestExit();
|
|
1666
|
+
return true;
|
|
1667
|
+
};
|
|
1668
|
+
const saveActiveSession = async () => {
|
|
1669
|
+
try {
|
|
1670
|
+
await saveSessionBoth({ serverSessionClient, session });
|
|
1671
|
+
return true;
|
|
1672
|
+
}
|
|
1673
|
+
catch (error) {
|
|
1674
|
+
if (exitForAuthenticationError(error))
|
|
1675
|
+
return false;
|
|
1676
|
+
appendError(`Session save failed: ${error.message}`);
|
|
1677
|
+
return true;
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1664
1680
|
const openSudoPasswordPrompt = (command, prompt, signal) => new Promise((resolve) => {
|
|
1665
1681
|
if (exiting || signal?.aborted) {
|
|
1666
1682
|
resolve(null);
|
|
@@ -1797,7 +1813,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1797
1813
|
selectedModelId: selected,
|
|
1798
1814
|
serverModels: fresh,
|
|
1799
1815
|
});
|
|
1800
|
-
await saveActiveSession()
|
|
1816
|
+
if (!(await saveActiveSession()))
|
|
1817
|
+
return;
|
|
1801
1818
|
syncShellStateFromSession();
|
|
1802
1819
|
appendStaticEntry({
|
|
1803
1820
|
body: `Switched to ${formatModelLabel(selected, fresh.models)}. Conversation history preserved.`,
|
|
@@ -1916,6 +1933,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1916
1933
|
await switchToModel(selected.id);
|
|
1917
1934
|
}
|
|
1918
1935
|
catch (error) {
|
|
1936
|
+
if (exitForAuthenticationError(error))
|
|
1937
|
+
return;
|
|
1919
1938
|
appendError(error.message);
|
|
1920
1939
|
}
|
|
1921
1940
|
};
|
|
@@ -1999,6 +2018,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1999
2018
|
await remountTui();
|
|
2000
2019
|
}
|
|
2001
2020
|
catch (error) {
|
|
2021
|
+
if (exitForAuthenticationError(error))
|
|
2022
|
+
return;
|
|
2002
2023
|
store.update((next) => ({
|
|
2003
2024
|
...next,
|
|
2004
2025
|
busy: false,
|
|
@@ -2151,14 +2172,18 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2151
2172
|
});
|
|
2152
2173
|
}
|
|
2153
2174
|
catch (error) {
|
|
2175
|
+
if (exitForAuthenticationError(error))
|
|
2176
|
+
return;
|
|
2154
2177
|
appendError(error.message);
|
|
2155
2178
|
}
|
|
2156
2179
|
finally {
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2180
|
+
if (!exiting) {
|
|
2181
|
+
store.update((current) => ({
|
|
2182
|
+
...current,
|
|
2183
|
+
busy: false,
|
|
2184
|
+
status: 'Ready',
|
|
2185
|
+
}));
|
|
2186
|
+
}
|
|
2162
2187
|
}
|
|
2163
2188
|
return;
|
|
2164
2189
|
}
|
|
@@ -2172,14 +2197,18 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2172
2197
|
await openResumePicker();
|
|
2173
2198
|
}
|
|
2174
2199
|
catch (error) {
|
|
2200
|
+
if (exitForAuthenticationError(error))
|
|
2201
|
+
return;
|
|
2175
2202
|
appendError(error.message);
|
|
2176
2203
|
}
|
|
2177
2204
|
finally {
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2205
|
+
if (!exiting) {
|
|
2206
|
+
store.update((current) => ({
|
|
2207
|
+
...current,
|
|
2208
|
+
busy: false,
|
|
2209
|
+
status: current.resumePickerOpen ? 'Select a session to resume' : 'Ready',
|
|
2210
|
+
}));
|
|
2211
|
+
}
|
|
2183
2212
|
}
|
|
2184
2213
|
return;
|
|
2185
2214
|
}
|
|
@@ -2199,14 +2228,18 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2199
2228
|
}
|
|
2200
2229
|
}
|
|
2201
2230
|
catch (error) {
|
|
2231
|
+
if (exitForAuthenticationError(error))
|
|
2232
|
+
return;
|
|
2202
2233
|
appendError(error.message);
|
|
2203
2234
|
}
|
|
2204
2235
|
finally {
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2236
|
+
if (!exiting) {
|
|
2237
|
+
store.update((current) => ({
|
|
2238
|
+
...current,
|
|
2239
|
+
busy: false,
|
|
2240
|
+
status: current.modelPickerOpen ? 'Select a model' : 'Ready',
|
|
2241
|
+
}));
|
|
2242
|
+
}
|
|
2210
2243
|
}
|
|
2211
2244
|
return;
|
|
2212
2245
|
}
|
|
@@ -2215,7 +2248,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2215
2248
|
clearTodos();
|
|
2216
2249
|
syncTodosState();
|
|
2217
2250
|
latestUsageSummary = null;
|
|
2218
|
-
await saveActiveSession()
|
|
2251
|
+
if (!(await saveActiveSession()))
|
|
2252
|
+
return;
|
|
2219
2253
|
store.replaceTranscript([
|
|
2220
2254
|
{
|
|
2221
2255
|
body: 'Conversation cleared.',
|
|
@@ -2274,7 +2308,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2274
2308
|
return;
|
|
2275
2309
|
latestUsageSummary = result.usageSummary ?? null;
|
|
2276
2310
|
const turnEntries = takePendingTurnEntries();
|
|
2277
|
-
await saveActiveSession()
|
|
2311
|
+
if (!(await saveActiveSession()))
|
|
2312
|
+
return;
|
|
2278
2313
|
syncShellStateFromSession();
|
|
2279
2314
|
store.update((current) => ({
|
|
2280
2315
|
...current,
|
|
@@ -2325,6 +2360,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2325
2360
|
return;
|
|
2326
2361
|
}
|
|
2327
2362
|
const cancelled = isTurnCancelledError(error);
|
|
2363
|
+
if (exitForAuthenticationError(error))
|
|
2364
|
+
return;
|
|
2328
2365
|
store.update((current) => ({
|
|
2329
2366
|
...current,
|
|
2330
2367
|
activeTurnInput: '',
|
|
@@ -2349,7 +2386,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2349
2386
|
title: 'System',
|
|
2350
2387
|
},
|
|
2351
2388
|
]);
|
|
2352
|
-
await saveActiveSession()
|
|
2389
|
+
if (!(await saveActiveSession()))
|
|
2390
|
+
return;
|
|
2353
2391
|
}
|
|
2354
2392
|
else {
|
|
2355
2393
|
appendStaticEntries(turnEntries);
|
|
@@ -2581,4 +2619,6 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2581
2619
|
setCommandOutputHook(null);
|
|
2582
2620
|
}
|
|
2583
2621
|
});
|
|
2622
|
+
if (fatalError)
|
|
2623
|
+
throw fatalError;
|
|
2584
2624
|
}
|
|
@@ -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 = [
|
|
@@ -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.7",
|
|
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.7",
|
|
41
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.7",
|
|
42
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.7",
|
|
43
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.7",
|
|
44
44
|
"@vscode/ripgrep": "1.18.0"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|