@thegitai/cli 1.0.0-preview.2 → 1.0.0-preview.4
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 +12 -4
- package/dist/bin/ai.js +46 -288
- package/dist/src/api/chat.js +2 -1
- package/dist/src/api/models.js +7 -2
- package/dist/src/help-text.js +16 -5
- package/dist/src/turn-failure-marker.js +11 -0
- package/dist/src/ui/repl.js +20 -13
- package/dist/src/ui/tui/build-frame.js +109 -62
- package/package.json +18 -6
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
# TheGitAI
|
|
1
|
+
# TheGitAI — AI coding agent for your terminal
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
TheGitAI is an AI coding agent for your terminal. It indexes your repository,
|
|
4
|
+
writes and edits files, runs commands, and builds features with you.
|
|
5
|
+
|
|
6
|
+
Talk to your repo in plain English. TheGitAI can keep long-running processes
|
|
7
|
+
going while you work, all with your approval.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
@@ -26,6 +28,12 @@ ai --version print the version and exit
|
|
|
26
28
|
|
|
27
29
|
Run `ai --help` for sessions, modes, keys, and chat commands.
|
|
28
30
|
|
|
31
|
+
Coding sessions require an interactive terminal on stdin and stdout. Piped
|
|
32
|
+
one-shot prompts are not supported. Saved history is local to the repo and can
|
|
33
|
+
always be viewed or resumed from that computer. Continuing it through the
|
|
34
|
+
service requires the TheGitAI account used for that session. Local sessions can
|
|
35
|
+
also be listed while signed out or offline.
|
|
36
|
+
|
|
29
37
|
## Visible to-do list
|
|
30
38
|
|
|
31
39
|
For larger multi-step tasks, the agent keeps a compact to-do list on screen so
|
package/dist/bin/ai.js
CHANGED
|
@@ -5,20 +5,16 @@ 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
7
|
import { isTransientNetworkError } from '../src/api/http.js';
|
|
8
|
-
import { formatCliHelpText
|
|
9
|
-
import { renderMarkdownForTerminal } from '../src/markdown-renderer.js';
|
|
8
|
+
import { formatCliHelpText } from '../src/help-text.js';
|
|
10
9
|
import { createIndex } from '../src/project-index.js';
|
|
11
|
-
import { createSession
|
|
10
|
+
import { createSession } from '../src/session.js';
|
|
12
11
|
import { applySessionSnapshot, listSessionMetadata, loadSessionSnapshot, saveSessionState, } from '../src/session-store.js';
|
|
13
|
-
import { runClientInteractive
|
|
12
|
+
import { runClientInteractive } from '../src/ui/repl.js';
|
|
14
13
|
import { appendPromptToFile } from '../src/ui/prompt-history-store.js';
|
|
15
14
|
import { formatSessionExitNotice } from '../src/session-exit.js';
|
|
16
15
|
import { formatUsageText } from '../src/usage.js';
|
|
17
16
|
import { formatVersionLine } from '../src/version.js';
|
|
18
17
|
import { parseArgs } from '../src/cli-args.js';
|
|
19
|
-
import { getJobBufferedOutput, killBackgroundJob, killAllBackgroundJobs, listBackgroundJobs, setBackgroundJobSession, } from '../src/background-jobs.js';
|
|
20
|
-
import { collectBackgroundJobUiKillMutations, collectBackgroundJobUiOutputMutations, } from '../src/tool-executor.js';
|
|
21
|
-
import { setScratchSession } from '../src/scratch-dir.js';
|
|
22
18
|
const DEFAULT_SERVER_URL = 'https://thegit.ai';
|
|
23
19
|
const { auth, chat, models, sessions } = ServerApi;
|
|
24
20
|
function printUsage() {
|
|
@@ -112,52 +108,12 @@ function printSessionList(rootDir, sessions, serverModels) {
|
|
|
112
108
|
}
|
|
113
109
|
}
|
|
114
110
|
}
|
|
115
|
-
function printStartupBanner(rootDir, modelLabel, autoYes) {
|
|
116
|
-
console.log(chalk.dim(`Project: ${rootDir}`));
|
|
117
|
-
console.log(chalk.dim(`Model: ${modelLabel}`));
|
|
118
|
-
if (autoYes) {
|
|
119
|
-
console.log(chalk.dim('Auto-confirm: enabled'));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function printSessionStartup(session) {
|
|
123
|
-
console.log(chalk.dim(`Session: ${session.sessionName ? `${session.sessionName} ` : ''}${session.sessionId}`));
|
|
124
|
-
}
|
|
125
111
|
function printSessionExit(session) {
|
|
126
112
|
console.log(chalk.dim(`\n${formatSessionExitNotice(session.sessionId)}\n`));
|
|
127
113
|
}
|
|
128
114
|
function modelLabel(serverModels, modelId) {
|
|
129
|
-
return (serverModels
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
function formatJobElapsedMs(ms) {
|
|
133
|
-
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
|
134
|
-
if (totalSeconds < 60)
|
|
135
|
-
return `${totalSeconds}s`;
|
|
136
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
137
|
-
if (minutes < 60)
|
|
138
|
-
return `${minutes}m${String(totalSeconds % 60).padStart(2, '0')}s`;
|
|
139
|
-
return `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, '0')}m`;
|
|
140
|
-
}
|
|
141
|
-
function makeConfirmCommand(session) {
|
|
142
|
-
return async (command) => {
|
|
143
|
-
console.log(chalk.bold(`\nCommand approval needed:\n${command}\n`));
|
|
144
|
-
const answer = (await promptText('Approve? [y]es/[a]ll/[n]o', 'n')).toLowerCase();
|
|
145
|
-
if (answer === 'a' || answer === 'all') {
|
|
146
|
-
session.autoYes = true;
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
return answer === 'y' || answer === 'yes';
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
function makeConfirmPatch(session) {
|
|
153
|
-
return async (filePath) => {
|
|
154
|
-
const answer = (await promptText(`Apply patch to ${filePath}? [y]es/[a]ll/[n]o`, 'n')).toLowerCase();
|
|
155
|
-
if (answer === 'a' || answer === 'all') {
|
|
156
|
-
session.autoYes = true;
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
return answer === 'y' || answer === 'yes';
|
|
160
|
-
};
|
|
115
|
+
return (serverModels?.models.find((model) => model.id === modelId)?.label ??
|
|
116
|
+
`Model ${modelId}`);
|
|
161
117
|
}
|
|
162
118
|
async function saveSessionBoth({ session, serverSessionClient, }) {
|
|
163
119
|
saveSessionState(session);
|
|
@@ -168,197 +124,18 @@ async function saveSessionBoth({ session, serverSessionClient, }) {
|
|
|
168
124
|
console.error(chalk.yellow(`Warning: session save failed: ${error.message}`));
|
|
169
125
|
}
|
|
170
126
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
selected: models.validateServerModel(selected, serverModels),
|
|
185
|
-
serverModels,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
async function promptForResumeSession(rootDir, serverModels) {
|
|
189
|
-
const sessions = listSessionMetadata(rootDir);
|
|
190
|
-
if (!sessions.length) {
|
|
191
|
-
console.log(chalk.dim('No saved sessions for this repo.\n'));
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
printSessionList(rootDir, sessions, serverModels);
|
|
195
|
-
console.log();
|
|
196
|
-
const identifier = (await promptText('Session id or name', '')).trim();
|
|
197
|
-
if (!identifier) {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
return loadSessionSnapshot(rootDir, identifier);
|
|
201
|
-
}
|
|
202
|
-
async function runTurn({ authConfig, projectIndex, serverSessionClient, session, inputText, }) {
|
|
203
|
-
const prompt = String(inputText ?? '').trim();
|
|
204
|
-
if (!prompt)
|
|
205
|
-
return;
|
|
206
|
-
appendPromptHistory(prompt, session.env);
|
|
207
|
-
const result = await chat.sendServerUserMessage({
|
|
208
|
-
config: authConfig,
|
|
209
|
-
projectIndex,
|
|
210
|
-
session,
|
|
211
|
-
input: prompt,
|
|
212
|
-
});
|
|
213
|
-
if (result.text) {
|
|
214
|
-
console.log(`\n${chalk.green('TheGitAI>')}`);
|
|
215
|
-
console.log(renderMarkdownForTerminal(result.text));
|
|
216
|
-
console.log();
|
|
217
|
-
}
|
|
218
|
-
await saveSessionBoth({ session, serverSessionClient });
|
|
219
|
-
}
|
|
220
|
-
async function mainInteractive({ authConfig, projectIndex, serverModels, serverSessionClient, session, usageText, initialPrompt, }) {
|
|
221
|
-
if (initialPrompt) {
|
|
222
|
-
console.log(chalk.dim(`Prompt: "${initialPrompt}"`));
|
|
223
|
-
await runTurn({
|
|
224
|
-
authConfig,
|
|
225
|
-
projectIndex,
|
|
226
|
-
serverSessionClient,
|
|
227
|
-
session,
|
|
228
|
-
inputText: initialPrompt,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
while (true) {
|
|
232
|
-
const inputText = await promptText('you');
|
|
233
|
-
const trimmed = inputText.trim();
|
|
234
|
-
if (!trimmed)
|
|
235
|
-
continue;
|
|
236
|
-
if (trimmed === '/exit')
|
|
237
|
-
return;
|
|
238
|
-
if (trimmed === '/help') {
|
|
239
|
-
console.log(renderMarkdownForTerminal(formatInteractiveHelpText()));
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (trimmed === '/usage') {
|
|
243
|
-
console.log(await usageText());
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
if (trimmed === '/clear') {
|
|
247
|
-
clearConversation(session);
|
|
248
|
-
await saveSessionBoth({ session, serverSessionClient });
|
|
249
|
-
console.log(chalk.dim('Conversation cleared.\n'));
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
if (trimmed === '/jobs' || trimmed.startsWith('/jobs ')) {
|
|
253
|
-
const jobsArgs = trimmed.slice('/jobs'.length).trim();
|
|
254
|
-
const killMatch = jobsArgs.match(/^kill\s+(\S+)$/);
|
|
255
|
-
if (killMatch) {
|
|
256
|
-
const jobId = killMatch[1];
|
|
257
|
-
const killed = await killBackgroundJob(jobId);
|
|
258
|
-
await collectBackgroundJobUiKillMutations({
|
|
259
|
-
session,
|
|
260
|
-
projectIndex,
|
|
261
|
-
jobId,
|
|
262
|
-
result: killed,
|
|
263
|
-
});
|
|
264
|
-
if (!killed.ok) {
|
|
265
|
-
console.log(chalk.red(killed.error ?? 'Background job kill failed.'));
|
|
266
|
-
}
|
|
267
|
-
else if (killed.alreadyFinished) {
|
|
268
|
-
console.log(chalk.dim(`${jobId} had already finished.`));
|
|
269
|
-
}
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
const outputMatch = jobsArgs.match(/^output\s+(\S+)$/);
|
|
273
|
-
if (outputMatch) {
|
|
274
|
-
const jobId = outputMatch[1];
|
|
275
|
-
await collectBackgroundJobUiOutputMutations({
|
|
276
|
-
session,
|
|
277
|
-
projectIndex,
|
|
278
|
-
jobId,
|
|
279
|
-
});
|
|
280
|
-
const job = listBackgroundJobs().find((candidate) => candidate.id === jobId);
|
|
281
|
-
const buffered = getJobBufferedOutput(jobId);
|
|
282
|
-
if (!job || !buffered) {
|
|
283
|
-
console.log(chalk.red(`Unknown background job id: ${jobId}`));
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
if (buffered.droppedChars > 0) {
|
|
287
|
-
console.log(chalk.dim(`... (${buffered.droppedChars} chars of older output dropped) ...`));
|
|
288
|
-
}
|
|
289
|
-
console.log(buffered.output || chalk.dim('(no output captured)'));
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
if (jobsArgs) {
|
|
293
|
-
console.log(chalk.dim('Usage: /jobs — list · /jobs output <id> — full output · /jobs kill <id> — kill'));
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
const jobsList = listBackgroundJobs();
|
|
297
|
-
if (!jobsList.length) {
|
|
298
|
-
console.log(chalk.dim('No background jobs in this session.'));
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
for (const job of jobsList) {
|
|
302
|
-
const elapsed = formatJobElapsedMs((job.endedAt ?? Date.now()) - job.startedAt);
|
|
303
|
-
const stateText = job.status === 'running'
|
|
304
|
-
? `running · ${elapsed}`
|
|
305
|
-
: job.status === 'killed'
|
|
306
|
-
? `killed · ran ${elapsed}`
|
|
307
|
-
: job.status === 'error'
|
|
308
|
-
? 'failed to start'
|
|
309
|
-
: `exited (code ${job.exitCode ?? 1}) · ran ${elapsed}`;
|
|
310
|
-
console.log(`${job.id} · ${stateText}\n $ ${job.command}`);
|
|
311
|
-
}
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
if (trimmed === '/resume') {
|
|
315
|
-
const snapshot = await promptForResumeSession(session.rootDir, serverModels);
|
|
316
|
-
if (!snapshot) {
|
|
317
|
-
console.log(chalk.dim('Resume cancelled.\n'));
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
applySessionSnapshot(session, snapshot);
|
|
321
|
-
setBackgroundJobSession(session.sessionId);
|
|
322
|
-
setScratchSession(session.sessionId);
|
|
323
|
-
await saveSessionBoth({ session, serverSessionClient });
|
|
324
|
-
console.log(chalk.dim(`Resumed session${session.sessionName ? ` "${session.sessionName}"` : ''} (${session.sessionId})\n`));
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
if (trimmed === '/model' || trimmed.startsWith('/model ')) {
|
|
328
|
-
const inline = trimmed.slice('/model'.length).trim();
|
|
329
|
-
let serverModels;
|
|
330
|
-
let selected = null;
|
|
331
|
-
if (inline) {
|
|
332
|
-
serverModels = await models.fetchServerModels({ config: authConfig });
|
|
333
|
-
selected = models.validateServerModel(inline, serverModels);
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
const response = await promptForModelSelection(session.modelId, authConfig);
|
|
337
|
-
serverModels = response.serverModels;
|
|
338
|
-
selected = response.selected;
|
|
339
|
-
}
|
|
340
|
-
if (!selected) {
|
|
341
|
-
console.log(chalk.dim('Model selection cancelled.\n'));
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
session.modelId = selected;
|
|
345
|
-
models.updateSelectedModelCache({
|
|
346
|
-
config: authConfig,
|
|
347
|
-
selectedModelId: selected,
|
|
348
|
-
serverModels,
|
|
349
|
-
});
|
|
350
|
-
await saveSessionBoth({ session, serverSessionClient });
|
|
351
|
-
console.log(chalk.dim(`Switched to ${modelLabel(serverModels, selected)}.\n`));
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
await runTurn({
|
|
355
|
-
authConfig,
|
|
356
|
-
projectIndex,
|
|
357
|
-
serverSessionClient,
|
|
358
|
-
session,
|
|
359
|
-
inputText: trimmed,
|
|
360
|
-
});
|
|
361
|
-
}
|
|
127
|
+
function requireInteractiveTerminal() {
|
|
128
|
+
if (process.stdin.isTTY !== true) {
|
|
129
|
+
console.error('Error: stdin is not a terminal');
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (process.stdout.isTTY !== true) {
|
|
134
|
+
console.error('Error: stdout is not a terminal');
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
362
139
|
}
|
|
363
140
|
export async function main() {
|
|
364
141
|
const { autoYes, help, version, usage, command, commandArgs, session: sessionIdentifier, listSessions, unknownOption, prompt, } = parseArgs(process.argv);
|
|
@@ -386,6 +163,22 @@ export async function main() {
|
|
|
386
163
|
return;
|
|
387
164
|
}
|
|
388
165
|
const rootDir = process.cwd();
|
|
166
|
+
if (listSessions) {
|
|
167
|
+
const activeServerUrl = auth.readCliAuthConfig()?.serverUrl ?? DEFAULT_SERVER_URL;
|
|
168
|
+
printSessionList(rootDir, listSessionMetadata(rootDir), models.selectCacheForServer(models.readCachedServerModels(), activeServerUrl));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const sourceSnapshot = sessionIdentifier
|
|
172
|
+
? loadSessionSnapshot(rootDir, sessionIdentifier)
|
|
173
|
+
: null;
|
|
174
|
+
if (sessionIdentifier && !sourceSnapshot) {
|
|
175
|
+
console.error(`Error: No saved session named or identified by "${sessionIdentifier}" is available for this repo. Run \`ai --list-sessions\`.`);
|
|
176
|
+
process.exitCode = 1;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!requireInteractiveTerminal()) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
389
182
|
const authConfig = requireCliAuthConfig();
|
|
390
183
|
const serverSessionClient = sessions.createServerSessionClient({ config: authConfig });
|
|
391
184
|
const cachedModels = models.selectCacheForServer(models.readCachedServerModels(), authConfig.serverUrl);
|
|
@@ -428,13 +221,6 @@ export async function main() {
|
|
|
428
221
|
if (offlineNotice) {
|
|
429
222
|
console.error(chalk.yellow(`⚠ Couldn't reach TheGitAI (${offlineNotice}). Starting with cached settings — it will reconnect on your next message.`));
|
|
430
223
|
}
|
|
431
|
-
if (listSessions) {
|
|
432
|
-
printSessionList(rootDir, listSessionMetadata(rootDir), serverModels);
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
const sourceSnapshot = sessionIdentifier
|
|
436
|
-
? loadSessionSnapshot(rootDir, sessionIdentifier)
|
|
437
|
-
: null;
|
|
438
224
|
const selectedModelId = models.selectServerModel({
|
|
439
225
|
requestedModelId: sourceSnapshot?.modelId ?? null,
|
|
440
226
|
cached: cachedModels,
|
|
@@ -450,13 +236,9 @@ export async function main() {
|
|
|
450
236
|
autoYes,
|
|
451
237
|
modelId: selectedModelId,
|
|
452
238
|
});
|
|
453
|
-
session.confirmCommand = makeConfirmCommand(session);
|
|
454
|
-
session.confirmPatch = makeConfirmPatch(session);
|
|
455
239
|
if (sourceSnapshot) {
|
|
456
240
|
applySessionSnapshot(session, sourceSnapshot);
|
|
457
|
-
await saveSessionBoth({ session, serverSessionClient });
|
|
458
241
|
}
|
|
459
|
-
setScratchSession(session.sessionId);
|
|
460
242
|
const projectIndex = createIndex({
|
|
461
243
|
rootDir,
|
|
462
244
|
onStatus: (message) => {
|
|
@@ -471,43 +253,19 @@ export async function main() {
|
|
|
471
253
|
},
|
|
472
254
|
});
|
|
473
255
|
const initialPrompt = prompt || undefined;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
printSessionExit(session);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
printStartupBanner(rootDir, modelLabel(serverModels, session.modelId), session.autoYes);
|
|
491
|
-
printSessionStartup(session);
|
|
492
|
-
setBackgroundJobSession(session.sessionId);
|
|
493
|
-
try {
|
|
494
|
-
await mainInteractive({
|
|
495
|
-
authConfig,
|
|
496
|
-
projectIndex,
|
|
497
|
-
serverModels,
|
|
498
|
-
serverSessionClient,
|
|
499
|
-
session,
|
|
500
|
-
usageText: async () => formatUsageText(await auth.fetchWhoamiResponse({ config: authConfig })),
|
|
501
|
-
initialPrompt,
|
|
502
|
-
});
|
|
503
|
-
await saveSessionBoth({ session, serverSessionClient });
|
|
504
|
-
printSessionExit(session);
|
|
505
|
-
}
|
|
506
|
-
finally {
|
|
507
|
-
killAllBackgroundJobs({ sessionId: session.sessionId, remove: true });
|
|
508
|
-
setBackgroundJobSession(null);
|
|
509
|
-
setScratchSession(null);
|
|
510
|
-
}
|
|
256
|
+
await runClientInteractive({
|
|
257
|
+
appendPromptHistory: (value) => appendPromptHistory(value, session.env),
|
|
258
|
+
authConfig,
|
|
259
|
+
debugUi: whoami.debugUi,
|
|
260
|
+
projectIndex,
|
|
261
|
+
serverModels,
|
|
262
|
+
serverSessionClient,
|
|
263
|
+
session,
|
|
264
|
+
initialPrompt,
|
|
265
|
+
usageText: async () => formatUsageText(await auth.fetchWhoamiResponse({ config: authConfig })),
|
|
266
|
+
});
|
|
267
|
+
await saveSessionBoth({ session, serverSessionClient });
|
|
268
|
+
printSessionExit(session);
|
|
511
269
|
}
|
|
512
270
|
main().catch((error) => {
|
|
513
271
|
console.error(chalk.red(`\n✖ Error: ${error.message}\n`));
|
package/dist/src/api/chat.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createTraceContext, normalizeServerUrl, readErrorResponse, } from './ht
|
|
|
6
6
|
import { collectClientEnvironment } from '../client-environment.js';
|
|
7
7
|
import { collectProjectOrientation } from '../project-orientation.js';
|
|
8
8
|
import { autoAttachImages } from '../core/image-path-extractor.js';
|
|
9
|
+
import { formatTurnFailureMarker } from '../turn-failure-marker.js';
|
|
9
10
|
export class TurnCancelledError extends Error {
|
|
10
11
|
name = 'TurnCancelledError';
|
|
11
12
|
constructor(message = 'Turn cancelled.') {
|
|
@@ -102,7 +103,7 @@ function preserveFailedTurnInput(session, input, category) {
|
|
|
102
103
|
session.history.push({ role: 'user', parts: [{ text }], kind: 'turnStart' });
|
|
103
104
|
session.history.push({
|
|
104
105
|
role: 'model',
|
|
105
|
-
parts: [{ text:
|
|
106
|
+
parts: [{ text: formatTurnFailureMarker(category) }],
|
|
106
107
|
});
|
|
107
108
|
}
|
|
108
109
|
function historyHasToolCall(session, callId) {
|
package/dist/src/api/models.js
CHANGED
|
@@ -9,10 +9,15 @@ function sanitizeModelInfo(raw) {
|
|
|
9
9
|
const value = raw;
|
|
10
10
|
const id = Number(value.id);
|
|
11
11
|
const label = String(value.label ?? '').trim();
|
|
12
|
-
|
|
12
|
+
const costRating = Number(value.costRating);
|
|
13
|
+
const description = String(value.description ?? '').trim();
|
|
14
|
+
if (!Number.isInteger(id) || id <= 0 || !label || !isCostRating(costRating)) {
|
|
13
15
|
return null;
|
|
14
16
|
}
|
|
15
|
-
return { id, label };
|
|
17
|
+
return { id, label, costRating, description };
|
|
18
|
+
}
|
|
19
|
+
function isCostRating(value) {
|
|
20
|
+
return Number.isInteger(value) && value >= 1 && value <= 3;
|
|
16
21
|
}
|
|
17
22
|
export function getModelsCachePath(env = process.env) {
|
|
18
23
|
return path.join(getClientStateDir(env), 'models.json');
|
package/dist/src/help-text.js
CHANGED
|
@@ -22,6 +22,7 @@ const HELP_MARKDOWN = [
|
|
|
22
22
|
'',
|
|
23
23
|
'- `ai` — start an interactive chat session in the current repo',
|
|
24
24
|
'- `ai "<request>"` — start an interactive session with `<request>` as the first message',
|
|
25
|
+
'- Coding sessions require terminal stdin and stdout; piped prompts are not supported.',
|
|
25
26
|
'',
|
|
26
27
|
'## Auth',
|
|
27
28
|
'',
|
|
@@ -35,8 +36,8 @@ const HELP_MARKDOWN = [
|
|
|
35
36
|
'',
|
|
36
37
|
'- `ai --list-sessions` — list saved sessions for this repo',
|
|
37
38
|
'- `ai --session <id|name>` — resume a saved session by id or name',
|
|
38
|
-
'- Sessions are stored locally and
|
|
39
|
-
'
|
|
39
|
+
'- Sessions are stored locally and can be listed or resumed in the same repo.',
|
|
40
|
+
' Continuing one requires the TheGitAI account used for that session.',
|
|
40
41
|
'',
|
|
41
42
|
'## Options',
|
|
42
43
|
'',
|
|
@@ -105,7 +106,10 @@ const HELP_MARKDOWN = [
|
|
|
105
106
|
'- Auth or permission errors → run `ai whoami` to confirm the signed-in',
|
|
106
107
|
' account.',
|
|
107
108
|
'- Usage or quota errors → run `ai --usage`.',
|
|
108
|
-
'-
|
|
109
|
+
'- Signed in with the wrong credentials → `ai logout`, then `ai login` with',
|
|
110
|
+
' the account you intended to use.',
|
|
111
|
+
'- A local session was used with a different sign-in → sign in with the',
|
|
112
|
+
' account you used for that session or start a new session.',
|
|
109
113
|
'- For anything else, re-run the command and report the printed error',
|
|
110
114
|
' message — there is no client-side debug mode by design.',
|
|
111
115
|
].join('\n');
|
|
@@ -126,8 +130,15 @@ export function formatInteractiveHelpText() {
|
|
|
126
130
|
return HELP_MARKDOWN;
|
|
127
131
|
}
|
|
128
132
|
export function formatCliHelpText({ color = false } = {}) {
|
|
129
|
-
if (!color)
|
|
130
|
-
return HELP_MARKDOWN
|
|
133
|
+
if (!color) {
|
|
134
|
+
return HELP_MARKDOWN.split('\n')
|
|
135
|
+
.map((line) => line
|
|
136
|
+
.replace(/^#{1,6}\s+/, '')
|
|
137
|
+
.replace(/^-\s+/, ' ')
|
|
138
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
139
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1'))
|
|
140
|
+
.join('\n');
|
|
141
|
+
}
|
|
131
142
|
return HELP_MARKDOWN.split('\n')
|
|
132
143
|
.map((line) => {
|
|
133
144
|
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const TURN_FAILURE_MARKER_PATTERN = /^Turn failed before completion: ([a-z][a-z0-9_-]*)\.?$/i;
|
|
2
|
+
export function formatTurnFailureMarker(category) {
|
|
3
|
+
const normalized = category.trim().toLowerCase();
|
|
4
|
+
const safeCategory = /^[a-z][a-z0-9_-]*$/.test(normalized)
|
|
5
|
+
? normalized
|
|
6
|
+
: 'unknown_error';
|
|
7
|
+
return `Turn failed before completion: ${safeCategory}.`;
|
|
8
|
+
}
|
|
9
|
+
export function isTurnFailureMarker(text) {
|
|
10
|
+
return TURN_FAILURE_MARKER_PATTERN.test(text.trim());
|
|
11
|
+
}
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getJobBufferedOutput, getJobOutputPreview, hasRunningBackgroundJobs, ki
|
|
|
9
9
|
import { clearTodos, listTodos, setTodoSession } from '../todo-list.js';
|
|
10
10
|
import { setScratchSession } from '../scratch-dir.js';
|
|
11
11
|
import { cancelActiveCommand } from '../executor.js';
|
|
12
|
+
import { isTurnFailureMarker } from '../turn-failure-marker.js';
|
|
12
13
|
import { setCommandOutputHook, withTuiMode } from '../runtime-mode.js';
|
|
13
14
|
import { collectBackgroundJobUiKillMutations, collectBackgroundJobUiOutputMutations, } from '../tool-executor.js';
|
|
14
15
|
import { clearConversation, } from '../session.js';
|
|
@@ -780,7 +781,7 @@ function displayUserTextFromHistoryEntry(entry) {
|
|
|
780
781
|
.slice(contentStart, contentEnd === -1 ? text.length : contentEnd)
|
|
781
782
|
.trim();
|
|
782
783
|
}
|
|
783
|
-
function buildTranscriptFromSessionHistory(history) {
|
|
784
|
+
export function buildTranscriptFromSessionHistory(history) {
|
|
784
785
|
const entries = [];
|
|
785
786
|
const pendingCalls = new Map();
|
|
786
787
|
for (const entry of history) {
|
|
@@ -826,6 +827,14 @@ function buildTranscriptFromSessionHistory(history) {
|
|
|
826
827
|
}
|
|
827
828
|
const text = textFromHistoryEntry(entry);
|
|
828
829
|
if ((entry.role === 'model' || entry.role === 'assistant') && text) {
|
|
830
|
+
if (isTurnFailureMarker(text)) {
|
|
831
|
+
entries.push({
|
|
832
|
+
body: 'This request did not complete.',
|
|
833
|
+
kind: 'system',
|
|
834
|
+
title: 'Previous turn',
|
|
835
|
+
});
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
829
838
|
entries.push({ body: text, kind: 'assistant', title: 'Response' });
|
|
830
839
|
}
|
|
831
840
|
}
|
|
@@ -1238,7 +1247,11 @@ export function buildModelPickerOptions(currentModelId, serverModels) {
|
|
|
1238
1247
|
return serverModels.map((model) => ({
|
|
1239
1248
|
id: model.id,
|
|
1240
1249
|
label: model.label,
|
|
1241
|
-
|
|
1250
|
+
publicId: model.id,
|
|
1251
|
+
costRating: model.costRating,
|
|
1252
|
+
current: model.id === currentModelId,
|
|
1253
|
+
disabled: false,
|
|
1254
|
+
note: model.description,
|
|
1242
1255
|
}));
|
|
1243
1256
|
}
|
|
1244
1257
|
function getDefaultModelPickerIndex(currentModelId, serverModels) {
|
|
@@ -1269,17 +1282,12 @@ async function saveSessionBoth({ serverSessionClient, session, }) {
|
|
|
1269
1282
|
saveSessionState(session);
|
|
1270
1283
|
await serverSessionClient.save(session);
|
|
1271
1284
|
}
|
|
1272
|
-
function shouldUseRatatuiShell() {
|
|
1273
|
-
if (process.env.THEGITAI_PLAIN === '1')
|
|
1274
|
-
return false;
|
|
1275
|
-
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1276
|
-
}
|
|
1277
|
-
export function shouldUseClientRatatuiShell() {
|
|
1278
|
-
return shouldUseRatatuiShell();
|
|
1279
|
-
}
|
|
1280
1285
|
export async function runClientInteractive({ appendPromptHistory, authConfig, debugUi, projectIndex, serverModels, serverSessionClient, session, usageText, initialPrompt, }) {
|
|
1281
|
-
if (
|
|
1282
|
-
throw new Error('
|
|
1286
|
+
if (process.stdin.isTTY !== true) {
|
|
1287
|
+
throw new Error('stdin is not a terminal');
|
|
1288
|
+
}
|
|
1289
|
+
if (process.stdout.isTTY !== true) {
|
|
1290
|
+
throw new Error('stdout is not a terminal');
|
|
1283
1291
|
}
|
|
1284
1292
|
await withTuiMode(async () => {
|
|
1285
1293
|
setBackgroundJobSession(session.sessionId);
|
|
@@ -1964,7 +1972,6 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1964
1972
|
syncBackgroundJobsState();
|
|
1965
1973
|
setTodoSession(session.sessionId);
|
|
1966
1974
|
syncTodosState();
|
|
1967
|
-
await saveActiveSession();
|
|
1968
1975
|
syncShellStateFromSession();
|
|
1969
1976
|
store.update((next) => ({
|
|
1970
1977
|
...next,
|
|
@@ -17,9 +17,9 @@ const OVERLAY_PANEL_MAX_WIDTH = 86;
|
|
|
17
17
|
const OVERLAY_PANEL_MARGIN_LINES = 2;
|
|
18
18
|
const OVERLAY_BORDER_COLOR = 'yellow';
|
|
19
19
|
const OVERLAY_WARNING_COLOR = 'ansi256(208)';
|
|
20
|
-
const MODEL_PICKER_PANEL_MAX_WIDTH =
|
|
20
|
+
const MODEL_PICKER_PANEL_MAX_WIDTH = 144;
|
|
21
21
|
const MODEL_PICKER_PANEL_MARGIN_LINES = 2;
|
|
22
|
-
const MODEL_PICKER_BORDER_COLOR = '
|
|
22
|
+
const MODEL_PICKER_BORDER_COLOR = 'cyan';
|
|
23
23
|
const MODEL_PICKER_ACCENT_COLOR = 'cyan';
|
|
24
24
|
const MODEL_PICKER_HIGHLIGHT_BG = 'ansi256(87)';
|
|
25
25
|
const MODEL_PICKER_META_INDENT = ' ';
|
|
@@ -170,8 +170,11 @@ function buildModelPickerOptions(currentModelId, serverModels) {
|
|
|
170
170
|
return serverModels.map((model) => ({
|
|
171
171
|
id: model.id,
|
|
172
172
|
label: model.label,
|
|
173
|
-
|
|
173
|
+
publicId: model.id,
|
|
174
|
+
costRating: model.costRating,
|
|
175
|
+
current: model.id === currentModelId,
|
|
174
176
|
disabled: false,
|
|
177
|
+
note: model.description,
|
|
175
178
|
}));
|
|
176
179
|
}
|
|
177
180
|
function getInputCommandToken(input) {
|
|
@@ -598,77 +601,118 @@ function padSpansToInnerWidth(spans, innerWidth, fill) {
|
|
|
598
601
|
function modelPickerPanelSideLine(content, innerWidth) {
|
|
599
602
|
return overlayPanelLine(content, innerWidth, MODEL_PICKER_BORDER_COLOR);
|
|
600
603
|
}
|
|
604
|
+
const MODEL_PICKER_COST_WIDTH = 6;
|
|
605
|
+
const MODEL_PICKER_MODEL_WIDTH = 42;
|
|
606
|
+
const MODEL_PICKER_NUMBER_WIDTH = 4;
|
|
607
|
+
const MODEL_PICKER_SEPARATOR = ' │ ';
|
|
608
|
+
const MODEL_PICKER_WIDE_MIN_WIDTH = 78;
|
|
601
609
|
function modelPickerTopBorder(panelWidth) {
|
|
602
|
-
const
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
610
|
+
const fullTitle = ' TheGitAI - Model Selection ';
|
|
611
|
+
const compactTitle = ' Model Selection ';
|
|
612
|
+
const title = panelWidth >= fullTitle.length + 4 ? fullTitle : compactTitle;
|
|
613
|
+
const available = Math.max(0, panelWidth - 2 - [...title].length);
|
|
614
|
+
const left = Math.floor(available / 2);
|
|
615
|
+
const right = available - left;
|
|
616
|
+
return line(span(`╭${'─'.repeat(left)}`, { color: MODEL_PICKER_BORDER_COLOR }), span(title, { color: MODEL_PICKER_ACCENT_COLOR, bold: true }), span(`${'─'.repeat(right)}╮`, { color: MODEL_PICKER_BORDER_COLOR }));
|
|
617
|
+
}
|
|
618
|
+
function modelPickerDivider(panelWidth) {
|
|
619
|
+
return plainLine(`├${'─'.repeat(panelWidth - 2)}┤`, {
|
|
620
|
+
color: MODEL_PICKER_BORDER_COLOR,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
function modelPickerCell(text, width, style = {}) {
|
|
624
|
+
const fitted = fitLine(text, width);
|
|
625
|
+
return span(`${fitted}${' '.repeat(Math.max(0, width - [...fitted].length))}`, style);
|
|
626
|
+
}
|
|
627
|
+
function modelPickerCostText(rating) {
|
|
628
|
+
const steps = Math.max(1, Math.min(3, Math.round(rating)));
|
|
629
|
+
return '$'.repeat(steps);
|
|
630
|
+
}
|
|
631
|
+
function modelPickerNotesWidth(innerWidth) {
|
|
632
|
+
return Math.max(18, innerWidth -
|
|
633
|
+
MODEL_PICKER_NUMBER_WIDTH -
|
|
634
|
+
MODEL_PICKER_MODEL_WIDTH -
|
|
635
|
+
MODEL_PICKER_COST_WIDTH -
|
|
636
|
+
MODEL_PICKER_SEPARATOR.length * 2);
|
|
637
|
+
}
|
|
638
|
+
function modelPickerModelSpans(option, selected, width, showCurrentTag, labelStyle, selectedStyle) {
|
|
639
|
+
const tag = option.current && showCurrentTag ? ' (current)' : '';
|
|
640
|
+
const labelWidth = Math.max(1, width - [...tag].length);
|
|
641
|
+
const label = fitLine(`${selected ? '▶ ' : ' '}${option.label}`, labelWidth);
|
|
642
|
+
const used = [...label].length + [...tag].length;
|
|
643
|
+
return [
|
|
644
|
+
span(label, { ...labelStyle, ...selectedStyle }),
|
|
645
|
+
span(tag, { color: 'gray', ...selectedStyle }),
|
|
646
|
+
span(' '.repeat(Math.max(0, width - used)), selectedStyle),
|
|
647
|
+
];
|
|
606
648
|
}
|
|
607
649
|
function modelPickerItemLines(option, selected, innerWidth) {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
span('
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
], innerWidth, highlight);
|
|
628
|
-
const lines = [modelPickerPanelSideLine(line(...titleSpans), innerWidth)];
|
|
629
|
-
if (option.meta) {
|
|
630
|
-
const metaSpans = padSpansToInnerWidth([
|
|
631
|
-
span(`${MODEL_PICKER_META_INDENT}${option.meta}`, {
|
|
632
|
-
color: 'gray',
|
|
633
|
-
...highlight,
|
|
634
|
-
}),
|
|
635
|
-
], innerWidth, highlight);
|
|
636
|
-
lines.push(modelPickerPanelSideLine(line(...metaSpans), innerWidth));
|
|
637
|
-
}
|
|
638
|
-
return lines;
|
|
650
|
+
const selectedStyle = selected ? { bgColor: MODEL_PICKER_HIGHLIGHT_BG } : {};
|
|
651
|
+
const labelStyle = option.disabled
|
|
652
|
+
? { color: 'gray' }
|
|
653
|
+
: selected
|
|
654
|
+
? { color: 'cyan', bold: true }
|
|
655
|
+
: {};
|
|
656
|
+
const numberCell = modelPickerCell(String(option.publicId), MODEL_PICKER_NUMBER_WIDTH, { color: 'cyan', bold: selected, ...selectedStyle });
|
|
657
|
+
const cost = modelPickerCostText(option.costRating);
|
|
658
|
+
if (innerWidth < MODEL_PICKER_WIDE_MIN_WIDTH) {
|
|
659
|
+
const modelWidth = Math.max(12, innerWidth - MODEL_PICKER_NUMBER_WIDTH - MODEL_PICKER_COST_WIDTH - 2);
|
|
660
|
+
const row = [
|
|
661
|
+
numberCell,
|
|
662
|
+
...modelPickerModelSpans(option, selected, modelWidth, false, labelStyle, selectedStyle),
|
|
663
|
+
span(' ', selectedStyle),
|
|
664
|
+
modelPickerCell(cost, MODEL_PICKER_COST_WIDTH, selectedStyle),
|
|
665
|
+
];
|
|
666
|
+
return [
|
|
667
|
+
modelPickerPanelSideLine(line(...padSpansToInnerWidth(row, innerWidth, selectedStyle)), innerWidth),
|
|
668
|
+
];
|
|
639
669
|
}
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
670
|
+
const row = [
|
|
671
|
+
numberCell,
|
|
672
|
+
...modelPickerModelSpans(option, selected, MODEL_PICKER_MODEL_WIDTH, true, labelStyle, selectedStyle),
|
|
673
|
+
span(MODEL_PICKER_SEPARATOR, { color: 'gray', ...selectedStyle }),
|
|
674
|
+
modelPickerCell(cost, MODEL_PICKER_COST_WIDTH, selectedStyle),
|
|
675
|
+
span(MODEL_PICKER_SEPARATOR, { color: 'gray', ...selectedStyle }),
|
|
676
|
+
modelPickerCell(option.note, modelPickerNotesWidth(innerWidth), {
|
|
677
|
+
color: 'gray',
|
|
678
|
+
...selectedStyle,
|
|
679
|
+
}),
|
|
680
|
+
];
|
|
681
|
+
return [
|
|
682
|
+
modelPickerPanelSideLine(line(...padSpansToInnerWidth(row, innerWidth, selectedStyle)), innerWidth),
|
|
643
683
|
];
|
|
644
|
-
if (option.meta) {
|
|
645
|
-
lines.push(modelPickerPanelSideLine(line(span(`${MODEL_PICKER_META_INDENT}${option.meta}`, { color: 'gray' })), innerWidth));
|
|
646
|
-
}
|
|
647
|
-
return lines;
|
|
648
684
|
}
|
|
649
|
-
function
|
|
650
|
-
|
|
685
|
+
function modelPickerHeaderLine(innerWidth) {
|
|
686
|
+
const heading = { color: MODEL_PICKER_ACCENT_COLOR, bold: true };
|
|
687
|
+
if (innerWidth < MODEL_PICKER_WIDE_MIN_WIDTH) {
|
|
688
|
+
return line(modelPickerCell('#', MODEL_PICKER_NUMBER_WIDTH, heading), modelPickerCell('Model', Math.max(1, innerWidth - MODEL_PICKER_NUMBER_WIDTH), heading));
|
|
689
|
+
}
|
|
690
|
+
return line(modelPickerCell('#', MODEL_PICKER_NUMBER_WIDTH, heading), modelPickerCell('Model', MODEL_PICKER_MODEL_WIDTH, heading), span(MODEL_PICKER_SEPARATOR, { color: 'gray' }), modelPickerCell('Cost', MODEL_PICKER_COST_WIDTH, heading), span(MODEL_PICKER_SEPARATOR, { color: 'gray' }), modelPickerCell('Notes', modelPickerNotesWidth(innerWidth), heading));
|
|
651
691
|
}
|
|
652
|
-
function buildModelPickerPanel(options, selectedIndex, width) {
|
|
692
|
+
function buildModelPickerPanel(options, selectedIndex, width, availableHeight) {
|
|
653
693
|
const panelWidth = Math.max(28, Math.min(width, MODEL_PICKER_PANEL_MAX_WIDTH));
|
|
654
694
|
const innerWidth = Math.max(1, panelWidth - 4);
|
|
655
|
-
const
|
|
695
|
+
const compactBodyLineCount = options.length + 6;
|
|
696
|
+
const spacerLineCount = Math.max(0, options.length - 1);
|
|
697
|
+
const useRowSpacing = compactBodyLineCount + spacerLineCount <= availableHeight;
|
|
698
|
+
const bodyLineCount = compactBodyLineCount + (useRowSpacing ? spacerLineCount : 0);
|
|
699
|
+
const marginLineCount = Math.max(0, Math.min(MODEL_PICKER_PANEL_MARGIN_LINES, Math.floor((availableHeight - bodyLineCount) / 2)));
|
|
700
|
+
const margin = Array.from({ length: marginLineCount }, () => plainLine(''));
|
|
656
701
|
const body = [
|
|
657
|
-
plainLine('TheGitAI - Model Selection', {
|
|
658
|
-
color: MODEL_PICKER_ACCENT_COLOR,
|
|
659
|
-
bold: true,
|
|
660
|
-
}),
|
|
661
|
-
plainLine(''),
|
|
662
702
|
modelPickerTopBorder(panelWidth),
|
|
663
|
-
modelPickerPanelSideLine(
|
|
703
|
+
modelPickerPanelSideLine(modelPickerHeaderLine(innerWidth), innerWidth),
|
|
704
|
+
modelPickerDivider(panelWidth),
|
|
664
705
|
];
|
|
665
706
|
options.forEach((option, index) => {
|
|
666
707
|
body.push(...modelPickerItemLines(option, index === selectedIndex, innerWidth));
|
|
667
|
-
if (index < options.length - 1) {
|
|
668
|
-
body.push(
|
|
708
|
+
if (useRowSpacing && index < options.length - 1) {
|
|
709
|
+
body.push(modelPickerPanelSideLine(plainLine(''), innerWidth));
|
|
669
710
|
}
|
|
670
711
|
});
|
|
671
|
-
|
|
712
|
+
const fullHint = '↑/↓ navigate • Enter select • Esc cancel';
|
|
713
|
+
const compactHint = '↑/↓ • enter • esc';
|
|
714
|
+
const hint = [...fullHint].length <= innerWidth ? fullHint : compactHint;
|
|
715
|
+
body.push(modelPickerDivider(panelWidth), modelPickerPanelSideLine(plainLine(fitLine(hint, innerWidth), { color: 'gray' }), innerWidth), plainLine(`╰${'─'.repeat(panelWidth - 2)}╯`, { color: MODEL_PICKER_BORDER_COLOR }));
|
|
672
716
|
return [...margin, ...body, ...margin];
|
|
673
717
|
}
|
|
674
718
|
function commandPaletteTopBorder(panelWidth) {
|
|
@@ -677,6 +721,9 @@ function commandPaletteTopBorder(panelWidth) {
|
|
|
677
721
|
const dashCount = Math.max(0, panelWidth - prefix.length - suffix.length);
|
|
678
722
|
return line(span('╭─ ', { color: MODEL_PICKER_BORDER_COLOR }), span('Commands', { color: MODEL_PICKER_ACCENT_COLOR, bold: true }), span(` ${'─'.repeat(dashCount)}╮`, { color: MODEL_PICKER_BORDER_COLOR }));
|
|
679
723
|
}
|
|
724
|
+
function commandPaletteSeparatorLine(innerWidth) {
|
|
725
|
+
return modelPickerPanelSideLine(line(span('┈'.repeat(Math.max(1, innerWidth)), { color: 'gray', dim: true })), innerWidth);
|
|
726
|
+
}
|
|
680
727
|
function commandPaletteItemLines(option, selected, innerWidth) {
|
|
681
728
|
const highlight = { bgColor: MODEL_PICKER_HIGHLIGHT_BG };
|
|
682
729
|
if (selected) {
|
|
@@ -729,13 +776,13 @@ function buildCommandPalettePanel(suggestions, selectedIndex, width) {
|
|
|
729
776
|
suggestions.forEach((suggestion, index) => {
|
|
730
777
|
body.push(...commandPaletteItemLines(suggestion, index === selectedIndex, innerWidth));
|
|
731
778
|
if (index < suggestions.length - 1) {
|
|
732
|
-
body.push(
|
|
779
|
+
body.push(commandPaletteSeparatorLine(innerWidth));
|
|
733
780
|
}
|
|
734
781
|
});
|
|
735
782
|
body.push(modelPickerPanelSideLine(plainLine(''), innerWidth), modelPickerPanelSideLine(line(span('─'.repeat(innerWidth), { color: MODEL_PICKER_BORDER_COLOR })), innerWidth), modelPickerPanelSideLine(plainLine('↑/↓ choose • Tab or Enter accept • Esc cancel', { color: 'gray' }), innerWidth), plainLine(`╰${'─'.repeat(panelWidth - 2)}╯`, { color: MODEL_PICKER_BORDER_COLOR }));
|
|
736
783
|
return [...margin, ...body, ...margin];
|
|
737
784
|
}
|
|
738
|
-
function buildOverlayLines(state, width, nowMs) {
|
|
785
|
+
function buildOverlayLines(state, width, height, nowMs) {
|
|
739
786
|
const lines = [];
|
|
740
787
|
const panelWidth = Math.max(24, Math.min(width, OVERLAY_PANEL_MAX_WIDTH));
|
|
741
788
|
const innerWidth = Math.max(1, panelWidth - 4);
|
|
@@ -808,7 +855,7 @@ function buildOverlayLines(state, width, nowMs) {
|
|
|
808
855
|
}
|
|
809
856
|
if (state.modelPickerOpen) {
|
|
810
857
|
const options = buildModelPickerOptions(state.currentModelId, state.serverModels);
|
|
811
|
-
lines.push(...buildModelPickerPanel(options, state.modelPickerIndex, width));
|
|
858
|
+
lines.push(...buildModelPickerPanel(options, state.modelPickerIndex, width, height));
|
|
812
859
|
}
|
|
813
860
|
if (state.jobsPickerOpen) {
|
|
814
861
|
lines.push(...buildJobsPickerLines(state, width, nowMs));
|
|
@@ -892,7 +939,7 @@ export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, n
|
|
|
892
939
|
lines: [...composerLines, plainLine(''), ...composerFooterLines(state)],
|
|
893
940
|
});
|
|
894
941
|
}
|
|
895
|
-
const overlayLines = buildOverlayLines(state, contentWidth, nowMs);
|
|
942
|
+
const overlayLines = buildOverlayLines(state, contentWidth, rows, nowMs);
|
|
896
943
|
if (overlayLines.length > 0) {
|
|
897
944
|
sections.push({ kind: 'overlay', lines: overlayLines });
|
|
898
945
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-preview.
|
|
4
|
-
"description": "TheGitAI
|
|
3
|
+
"version": "1.0.0-preview.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
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"ai-coding-agent",
|
|
8
|
+
"coding-agent",
|
|
9
|
+
"coding-assistant",
|
|
10
|
+
"terminal",
|
|
11
|
+
"cli",
|
|
12
|
+
"developer-tools"
|
|
13
|
+
],
|
|
5
14
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
15
|
"homepage": "https://thegit.ai",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"email": "support@thegit.ai"
|
|
18
|
+
},
|
|
7
19
|
"type": "module",
|
|
8
20
|
"engines": {
|
|
9
21
|
"node": ">=24"
|
|
@@ -25,10 +37,10 @@
|
|
|
25
37
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
26
38
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
27
39
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
28
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
29
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
30
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
31
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
40
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.4",
|
|
41
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.4",
|
|
42
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.4",
|
|
43
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.4",
|
|
32
44
|
"@vscode/ripgrep": "1.18.0"
|
|
33
45
|
},
|
|
34
46
|
"publishConfig": {
|