@thegitai/cli 1.0.0-beta.2 → 1.0.0-beta.5
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/api/auth.js +4 -2
- package/dist/src/api/browser-login.js +4 -3
- package/dist/src/api/chat.js +18 -9
- package/dist/src/api/http.js +32 -3
- package/dist/src/api/models.js +4 -2
- package/dist/src/core/clipboard.js +56 -0
- package/dist/src/ui/tui/bridge.js +2 -2
- package/dist/src/ui/tui/build-frame.js +5 -1
- package/dist/src/ui/tui/shell-input.js +9 -1
- package/package.json +5 -5
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 { authorizedJson, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
|
|
4
|
+
import { ServerApiError, authorizedJson, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, } 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) {
|
|
@@ -68,14 +68,16 @@ export async function fetchWhoamiResponse({ config, fetchImpl = globalThis.fetch
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
export async function logoutFromServer({ config, fetchImpl = globalThis.fetch, }) {
|
|
71
|
+
const trace = createTraceContext();
|
|
71
72
|
const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/auth/logout`, {
|
|
72
73
|
method: 'POST',
|
|
73
74
|
headers: {
|
|
74
75
|
authorization: `Bearer ${config.token}`,
|
|
76
|
+
...trace.headers,
|
|
75
77
|
},
|
|
76
78
|
});
|
|
77
79
|
if (!response.ok && response.status !== 401) {
|
|
78
80
|
const data = (await readJsonResponse(response));
|
|
79
|
-
throw new
|
|
81
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
|
|
80
82
|
}
|
|
81
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 { failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
|
|
5
|
+
import { ServerApiError, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
|
|
6
6
|
const DEFAULT_WEBSITE_URL = 'https://thegit.ai';
|
|
7
7
|
const DEFAULT_DEV_WEBSITE_URL = 'http://localhost:3002';
|
|
8
8
|
const DEFAULT_SERVER_URL = 'https://thegit.ai';
|
|
@@ -73,14 +73,15 @@ const RESULT_PAGE = (heading, detail) => `<!doctype html><html><head><meta chars
|
|
|
73
73
|
`p{color:#9aa0a6}</style></head><body><div class="card"><h1>${heading}</h1>` +
|
|
74
74
|
`<p>${detail}</p></div></body></html>`;
|
|
75
75
|
async function exchangeCodeForToken({ serverUrl, code, codeVerifier, fetchImpl, }) {
|
|
76
|
+
const trace = createTraceContext();
|
|
76
77
|
const response = await fetchImpl(`${serverUrl}/v1/cli/auth/token`, {
|
|
77
78
|
method: 'POST',
|
|
78
|
-
headers: { 'content-type': 'application/json' },
|
|
79
|
+
headers: { 'content-type': 'application/json', ...trace.headers },
|
|
79
80
|
body: JSON.stringify({ code, code_verifier: codeVerifier }),
|
|
80
81
|
});
|
|
81
82
|
const data = (await readJsonResponse(response));
|
|
82
83
|
if (!response.ok) {
|
|
83
|
-
throw new
|
|
84
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
|
|
84
85
|
}
|
|
85
86
|
const token = String(data?.token ?? '').trim();
|
|
86
87
|
const customer = data?.customer;
|
package/dist/src/api/chat.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createPromptCheckpoint, sanitizeSessionSafetyForServer, } from '../session-safety.js';
|
|
2
2
|
import { applySessionSnapshot, snapshotFromSession, } from '../session-store.js';
|
|
3
3
|
import { executeLocalToolCall } from '../tool-executor.js';
|
|
4
|
-
import { normalizeServerUrl, readErrorResponse, } from './http.js';
|
|
4
|
+
import { createTraceContext, normalizeServerUrl, readErrorResponse, } from './http.js';
|
|
5
5
|
export class TurnCancelledError extends Error {
|
|
6
6
|
name = 'TurnCancelledError';
|
|
7
7
|
constructor(message = 'Turn cancelled.') {
|
|
@@ -12,10 +12,12 @@ export class ChatTurnFailedError extends Error {
|
|
|
12
12
|
name = 'ChatTurnFailedError';
|
|
13
13
|
category;
|
|
14
14
|
retryable;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
traceId;
|
|
16
|
+
constructor(message, category = 'unknown_error', retryable = false, traceId = '') {
|
|
17
|
+
super(traceId ? `${message}\nTrace ID: ${traceId}` : message);
|
|
17
18
|
this.category = category;
|
|
18
19
|
this.retryable = retryable;
|
|
20
|
+
this.traceId = traceId;
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
export function isTurnCancelledError(error) {
|
|
@@ -139,17 +141,19 @@ function publicStatusMessage(data) {
|
|
|
139
141
|
return `Running ${toolName} locally...`;
|
|
140
142
|
return null;
|
|
141
143
|
}
|
|
142
|
-
async function postToolResult({ config, turnId, event, result, session, fetchImpl, }) {
|
|
144
|
+
async function postToolResult({ config, turnId, event, result, session, fetchImpl, traceId, }) {
|
|
143
145
|
const payload = {
|
|
144
146
|
toolCallId: event.call.id,
|
|
145
147
|
result,
|
|
146
148
|
toolState: toolStateFromSession(session),
|
|
147
149
|
};
|
|
150
|
+
const trace = createTraceContext(traceId);
|
|
148
151
|
const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/chat/turn/${encodeURIComponent(turnId)}/tool-result`, {
|
|
149
152
|
method: 'POST',
|
|
150
153
|
headers: {
|
|
151
154
|
authorization: `Bearer ${config.token}`,
|
|
152
155
|
'content-type': 'application/json',
|
|
156
|
+
...trace.headers,
|
|
153
157
|
},
|
|
154
158
|
body: JSON.stringify(payload),
|
|
155
159
|
});
|
|
@@ -157,10 +161,10 @@ async function postToolResult({ config, turnId, event, result, session, fetchImp
|
|
|
157
161
|
return;
|
|
158
162
|
}
|
|
159
163
|
if (!response.ok) {
|
|
160
|
-
throw await readErrorResponse(response);
|
|
164
|
+
throw await readErrorResponse(response, trace.traceId);
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
|
-
async function executeAndPostToolResult({ config, projectIndex, session, event, input, fetchImpl, signal, }) {
|
|
167
|
+
async function executeAndPostToolResult({ config, projectIndex, session, event, input, fetchImpl, signal, traceId, }) {
|
|
164
168
|
const turnId = String(event?.turnId ?? '').trim();
|
|
165
169
|
if (!turnId || !event?.call?.id || !event.call.name) {
|
|
166
170
|
throw new Error('Server emitted an invalid tool-call event.');
|
|
@@ -189,13 +193,14 @@ async function executeAndPostToolResult({ config, projectIndex, session, event,
|
|
|
189
193
|
result: rawResult,
|
|
190
194
|
session,
|
|
191
195
|
fetchImpl,
|
|
196
|
+
traceId,
|
|
192
197
|
});
|
|
193
198
|
}
|
|
194
199
|
finally {
|
|
195
200
|
session.turnState.id = previousTurnId;
|
|
196
201
|
}
|
|
197
202
|
}
|
|
198
|
-
async function consumeTurnStream({ response, config, projectIndex, session, input, fetchImpl, signal, }) {
|
|
203
|
+
async function consumeTurnStream({ response, config, projectIndex, session, input, fetchImpl, signal, traceId, }) {
|
|
199
204
|
if (!response.body) {
|
|
200
205
|
throw new Error('Server returned an empty chat stream.');
|
|
201
206
|
}
|
|
@@ -224,6 +229,7 @@ async function consumeTurnStream({ response, config, projectIndex, session, inpu
|
|
|
224
229
|
input,
|
|
225
230
|
fetchImpl,
|
|
226
231
|
signal,
|
|
232
|
+
traceId,
|
|
227
233
|
});
|
|
228
234
|
return;
|
|
229
235
|
}
|
|
@@ -243,7 +249,7 @@ async function consumeTurnStream({ response, config, projectIndex, session, inpu
|
|
|
243
249
|
if (event.event === 'cancelled') {
|
|
244
250
|
throw new TurnCancelledError(message);
|
|
245
251
|
}
|
|
246
|
-
throw new ChatTurnFailedError(message, typeof event.data?.category === 'string' ? event.data.category : 'unknown_error', Boolean(event.data?.retryable));
|
|
252
|
+
throw new ChatTurnFailedError(message, typeof event.data?.category === 'string' ? event.data.category : 'unknown_error', Boolean(event.data?.retryable), typeof event.data?.traceId === 'string' ? event.data.traceId : traceId);
|
|
247
253
|
}
|
|
248
254
|
}
|
|
249
255
|
while (true) {
|
|
@@ -287,6 +293,7 @@ export async function sendServerUserMessage({ config, projectIndex, session, inp
|
|
|
287
293
|
autoYes: session.autoYes,
|
|
288
294
|
agentMode: session.agentMode,
|
|
289
295
|
};
|
|
296
|
+
const trace = createTraceContext();
|
|
290
297
|
const preTurnHistoryLength = session.history.length;
|
|
291
298
|
const preserveOnAbort = () => preserveCancelledTurnInput(session, input);
|
|
292
299
|
if (signal?.aborted) {
|
|
@@ -302,12 +309,13 @@ export async function sendServerUserMessage({ config, projectIndex, session, inp
|
|
|
302
309
|
accept: 'text/event-stream',
|
|
303
310
|
authorization: `Bearer ${config.token}`,
|
|
304
311
|
'content-type': 'application/json',
|
|
312
|
+
...trace.headers,
|
|
305
313
|
},
|
|
306
314
|
body: JSON.stringify(request),
|
|
307
315
|
signal,
|
|
308
316
|
});
|
|
309
317
|
if (!response.ok) {
|
|
310
|
-
throw await readErrorResponse(response);
|
|
318
|
+
throw await readErrorResponse(response, trace.traceId);
|
|
311
319
|
}
|
|
312
320
|
const result = await consumeTurnStream({
|
|
313
321
|
response,
|
|
@@ -317,6 +325,7 @@ export async function sendServerUserMessage({ config, projectIndex, session, inp
|
|
|
317
325
|
input,
|
|
318
326
|
fetchImpl,
|
|
319
327
|
signal,
|
|
328
|
+
traceId: trace.traceId,
|
|
320
329
|
});
|
|
321
330
|
applySessionSnapshot(session, result.snapshot, { preserveAgentMode: true });
|
|
322
331
|
return {
|
package/dist/src/api/http.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
const DEFAULT_SERVER_URL = 'https://thegit.ai';
|
|
3
|
+
export const TRACE_ID_HEADER = 'x-thegitai-trace-id';
|
|
4
|
+
export const CLIENT_HEADER = 'x-thegitai-client';
|
|
5
|
+
export const CLIENT_PLATFORM_HEADER = 'x-thegitai-client-platform';
|
|
6
|
+
export class ServerApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
traceId;
|
|
9
|
+
constructor(message, status, traceId) {
|
|
10
|
+
super(traceId ? `${message}\nTrace ID: ${traceId}` : message);
|
|
11
|
+
this.name = 'ServerApiError';
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.traceId = traceId;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function createTraceId() {
|
|
17
|
+
return `tr_${randomUUID().replace(/-/g, '')}`;
|
|
18
|
+
}
|
|
19
|
+
export function createTraceContext(traceId = createTraceId()) {
|
|
20
|
+
return {
|
|
21
|
+
traceId,
|
|
22
|
+
headers: {
|
|
23
|
+
[TRACE_ID_HEADER]: traceId,
|
|
24
|
+
[CLIENT_HEADER]: 'cli',
|
|
25
|
+
[CLIENT_PLATFORM_HEADER]: `${process.platform}/${process.arch}`,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
2
29
|
export function normalizeServerUrl(serverUrl) {
|
|
3
30
|
const normalized = String(serverUrl || DEFAULT_SERVER_URL)
|
|
4
31
|
.trim()
|
|
@@ -22,15 +49,17 @@ export async function readJsonResponse(response) {
|
|
|
22
49
|
export function failureMessage(data, status) {
|
|
23
50
|
return String(data?.error?.message ?? data?.message ?? `Request failed with ${status}`);
|
|
24
51
|
}
|
|
25
|
-
export async function readErrorResponse(response) {
|
|
52
|
+
export async function readErrorResponse(response, traceId = response.headers.get(TRACE_ID_HEADER) ?? '') {
|
|
26
53
|
const data = await readJsonResponse(response);
|
|
27
|
-
return new
|
|
54
|
+
return new ServerApiError(failureMessage(data, response.status), response.status, traceId);
|
|
28
55
|
}
|
|
29
56
|
export async function authorizedJson({ config, path, method = 'GET', body = null, headers = {}, fetchImpl = globalThis.fetch, }) {
|
|
57
|
+
const trace = createTraceContext();
|
|
30
58
|
const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}${path}`, {
|
|
31
59
|
method,
|
|
32
60
|
headers: {
|
|
33
61
|
authorization: `Bearer ${config.token}`,
|
|
62
|
+
...trace.headers,
|
|
34
63
|
...headers,
|
|
35
64
|
...(body === null ? {} : { 'content-type': 'application/json' }),
|
|
36
65
|
},
|
|
@@ -38,7 +67,7 @@ export async function authorizedJson({ config, path, method = 'GET', body = null
|
|
|
38
67
|
});
|
|
39
68
|
const data = await readJsonResponse(response);
|
|
40
69
|
if (!response.ok) {
|
|
41
|
-
throw new
|
|
70
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
|
|
42
71
|
}
|
|
43
72
|
return data;
|
|
44
73
|
}
|
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 { failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
|
|
4
|
+
import { ServerApiError, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
|
|
5
5
|
function sanitizeModelInfo(raw) {
|
|
6
6
|
if (!raw || typeof raw !== 'object') {
|
|
7
7
|
return null;
|
|
@@ -54,14 +54,16 @@ export function writeCachedServerModels(cache, env = process.env) {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
export async function fetchServerModels({ config, fetchImpl = globalThis.fetch, }) {
|
|
57
|
+
const trace = createTraceContext();
|
|
57
58
|
const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/models`, {
|
|
58
59
|
headers: {
|
|
59
60
|
authorization: `Bearer ${config.token}`,
|
|
61
|
+
...trace.headers,
|
|
60
62
|
},
|
|
61
63
|
});
|
|
62
64
|
const data = (await readJsonResponse(response));
|
|
63
65
|
if (!response.ok) {
|
|
64
|
-
throw new
|
|
66
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
|
|
65
67
|
}
|
|
66
68
|
const models = Array.isArray(data?.models)
|
|
67
69
|
? data.models.map(sanitizeModelInfo).filter(Boolean)
|
|
@@ -21,6 +21,14 @@ export function isSupportedImageMimeType(mime) {
|
|
|
21
21
|
}
|
|
22
22
|
function whichSync(cmd) {
|
|
23
23
|
try {
|
|
24
|
+
if (process.platform === 'win32') {
|
|
25
|
+
execFileSync('where.exe', [cmd], {
|
|
26
|
+
stdio: 'ignore',
|
|
27
|
+
timeout: 2000,
|
|
28
|
+
windowsHide: true,
|
|
29
|
+
});
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
24
32
|
execFileSync('which', [cmd], { stdio: 'ignore', timeout: 2000 });
|
|
25
33
|
return true;
|
|
26
34
|
}
|
|
@@ -93,6 +101,52 @@ function readClipboardLinux() {
|
|
|
93
101
|
}
|
|
94
102
|
throw new ClipboardError('Clipboard contains no image data.', 'NO_IMAGE');
|
|
95
103
|
}
|
|
104
|
+
const WINDOWS_CLIPBOARD_IMAGE_PS = [
|
|
105
|
+
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;',
|
|
106
|
+
'$ErrorActionPreference = "Stop";',
|
|
107
|
+
'Add-Type -AssemblyName System.Drawing;',
|
|
108
|
+
'$img = Get-Clipboard -Format Image;',
|
|
109
|
+
'if ($null -eq $img) { exit 2 }',
|
|
110
|
+
'$ms = New-Object System.IO.MemoryStream;',
|
|
111
|
+
'$img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png);',
|
|
112
|
+
'[Convert]::ToBase64String($ms.ToArray())',
|
|
113
|
+
].join(' ');
|
|
114
|
+
function readClipboardWindows() {
|
|
115
|
+
try {
|
|
116
|
+
const b64 = execFileSync('powershell.exe', ['-NoProfile', '-Command', WINDOWS_CLIPBOARD_IMAGE_PS], {
|
|
117
|
+
encoding: 'utf-8',
|
|
118
|
+
timeout: 5000,
|
|
119
|
+
maxBuffer: MAX_IMAGE_SIZE_BYTES * 2,
|
|
120
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
121
|
+
windowsHide: true,
|
|
122
|
+
}).trim();
|
|
123
|
+
if (!b64) {
|
|
124
|
+
throw new ClipboardError('Clipboard contains no image data.', 'NO_IMAGE');
|
|
125
|
+
}
|
|
126
|
+
const buf = Buffer.from(b64, 'base64');
|
|
127
|
+
if (!buf.length) {
|
|
128
|
+
throw new ClipboardError('Clipboard contains no image data.', 'NO_IMAGE');
|
|
129
|
+
}
|
|
130
|
+
if (buf.length > MAX_IMAGE_SIZE_BYTES) {
|
|
131
|
+
throw new ClipboardError('Clipboard image exceeds 10MB size limit.', 'READ_FAILED');
|
|
132
|
+
}
|
|
133
|
+
return { base64Data: b64, mimeType: 'image/png' };
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (err instanceof ClipboardError)
|
|
137
|
+
throw err;
|
|
138
|
+
if (err?.status === 2) {
|
|
139
|
+
throw new ClipboardError('Clipboard contains no image data. Copy an image first (Win+Shift+S), then press Alt+V.', 'NO_IMAGE');
|
|
140
|
+
}
|
|
141
|
+
if (isMaxBufferError(err)) {
|
|
142
|
+
throw new ClipboardError('Clipboard image exceeds 10MB size limit.', 'READ_FAILED');
|
|
143
|
+
}
|
|
144
|
+
const detail = [err?.message, err?.stderr?.toString?.()?.trim()]
|
|
145
|
+
.filter(Boolean)
|
|
146
|
+
.join(' — ');
|
|
147
|
+
throw new ClipboardError(`Failed to read clipboard image on Windows: ${detail || 'unknown error'}`, 'READ_FAILED');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
96
150
|
function readClipboardDarwin() {
|
|
97
151
|
if (whichSync('pngpaste')) {
|
|
98
152
|
try {
|
|
@@ -123,6 +177,8 @@ export function readClipboardImage(platform = process.platform) {
|
|
|
123
177
|
return readClipboardLinux();
|
|
124
178
|
case 'darwin':
|
|
125
179
|
return readClipboardDarwin();
|
|
180
|
+
case 'win32':
|
|
181
|
+
return readClipboardWindows();
|
|
126
182
|
default:
|
|
127
183
|
throw new ClipboardError(`Clipboard image paste is not supported on ${platform}.`, 'NO_TOOL');
|
|
128
184
|
}
|
|
@@ -114,13 +114,13 @@ export function resolveTuiBinaryPath() {
|
|
|
114
114
|
export function spawnTuiProcess(options = {}) {
|
|
115
115
|
const binaryPath = resolveTuiBinaryPath();
|
|
116
116
|
return spawn(binaryPath, [], {
|
|
117
|
-
stdio: ['pipe', '
|
|
117
|
+
stdio: ['pipe', 'inherit', 'pipe'],
|
|
118
118
|
env: { ...process.env, ...options.env },
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
export function createRatatuiBridge() {
|
|
122
122
|
const child = spawnTuiProcess();
|
|
123
|
-
const rl = createInterface({ input: child.
|
|
123
|
+
const rl = createInterface({ input: child.stderr });
|
|
124
124
|
let eventHandler = null;
|
|
125
125
|
let closed = false;
|
|
126
126
|
rl.on('line', (line) => {
|
|
@@ -311,7 +311,11 @@ function composerFooterLines(state) {
|
|
|
311
311
|
? state.queuedMessage
|
|
312
312
|
? 'Enter re-queues • ↑ edit queued • Esc cancels queued'
|
|
313
313
|
: 'Enter queues • Esc / Ctrl+C cancel turn'
|
|
314
|
-
:
|
|
314
|
+
: process.platform === 'win32'
|
|
315
|
+
? 'Enter sends • Shift+Tab mode • Alt+V image • Esc cancel turn • Ctrl+C quits'
|
|
316
|
+
: process.platform === 'darwin'
|
|
317
|
+
? 'Enter sends • Shift+Tab mode • Ctrl+V image • Esc cancel turn • Ctrl+C quits'
|
|
318
|
+
: 'Enter sends • Shift+Tab mode • Ctrl+V image • Esc cancel turn • Ctrl+C quits';
|
|
315
319
|
const agentLabel = agentModeLabel(state.agentMode).padEnd(AGENT_MODE_LABEL_WIDTH);
|
|
316
320
|
const tokenUsageText = state.tokenUsage || formatClientTokenUsage(null);
|
|
317
321
|
const footerSpans = [
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
2
2
|
import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor, getApprovalChoiceForCursor, getInputCommandToken, getNextApprovalCursor, getNextModelPickerIndex, getSlashCommandSuggestions, insertAtCursor, isExactSlashCommandToken, navigatePromptHistory, resolveApprovalChoiceFromInput, shouldRemountLiveFrameForComposerInputChange, } from '../repl.js';
|
|
3
3
|
import { buildPastePlaceholder, shouldCollapsePaste, } from '../paste-collapse.js';
|
|
4
|
+
function isClipboardImagePasteKey(key) {
|
|
5
|
+
if (process.platform === 'win32') {
|
|
6
|
+
return ((key.ctrl || key.meta) &&
|
|
7
|
+
key.input.toLowerCase() === 'v' &&
|
|
8
|
+
!key.shift);
|
|
9
|
+
}
|
|
10
|
+
return key.ctrl && key.input === 'v' && !key.shift && !key.meta;
|
|
11
|
+
}
|
|
4
12
|
function shouldShowCommandPalette(state) {
|
|
5
13
|
if (state.busy ||
|
|
6
14
|
state.exiting ||
|
|
@@ -428,7 +436,7 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
428
436
|
});
|
|
429
437
|
return;
|
|
430
438
|
}
|
|
431
|
-
if (key
|
|
439
|
+
if (isClipboardImagePasteKey(key)) {
|
|
432
440
|
const current = store.getState();
|
|
433
441
|
if (current.busy)
|
|
434
442
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "TheGitAI CLI client (source-visible, proprietary)",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://thegit.ai",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"web-tree-sitter": "^0.26.6"
|
|
28
28
|
},
|
|
29
29
|
"optionalDependencies": {
|
|
30
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-beta.
|
|
31
|
-
"@thegitai/tui-darwin-x64": "1.0.0-beta.
|
|
32
|
-
"@thegitai/tui-linux-x64": "1.0.0-beta.
|
|
33
|
-
"@thegitai/tui-win32-x64": "1.0.0-beta.
|
|
30
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-beta.5",
|
|
31
|
+
"@thegitai/tui-darwin-x64": "1.0.0-beta.5",
|
|
32
|
+
"@thegitai/tui-linux-x64": "1.0.0-beta.5",
|
|
33
|
+
"@thegitai/tui-win32-x64": "1.0.0-beta.5"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|