@thegitai/cli 1.0.0-preview.36 → 1.0.0-preview.38
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/models.js +10 -0
- package/dist/src/edit-journal.js +33 -2
- package/dist/src/patcher.js +43 -4
- package/dist/src/tools/delete-file.js +5 -1
- package/dist/src/ui/repl.js +56 -6
- package/package.json +6 -6
package/dist/src/api/models.js
CHANGED
|
@@ -122,6 +122,16 @@ export function validateServerModel(modelId, serverModels) {
|
|
|
122
122
|
}
|
|
123
123
|
return requested;
|
|
124
124
|
}
|
|
125
|
+
export function resolveResumedSessionModel(storedModelId, serverModels) {
|
|
126
|
+
if (serverModels.models.some((model) => model.id === storedModelId)) {
|
|
127
|
+
return { modelId: storedModelId, fellBack: false };
|
|
128
|
+
}
|
|
129
|
+
const fallbackId = serverModels.models[0]?.id;
|
|
130
|
+
return {
|
|
131
|
+
modelId: fallbackId ?? storedModelId,
|
|
132
|
+
fellBack: fallbackId != null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
125
135
|
export function updateSelectedModelCache({ config, selectedModelId, serverModels, env = process.env, }) {
|
|
126
136
|
writeCachedServerModels({
|
|
127
137
|
serverUrl: normalizeServerUrl(config.serverUrl),
|
package/dist/src/edit-journal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { closeSync, existsSync, lstatSync, openSync, readFileSync, readSync, } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { BINARY_ARTIFACT_EXTENSIONS } from './artifact-policy.js';
|
|
6
6
|
import { resolveProjectPath } from './patcher.js';
|
|
@@ -30,13 +30,33 @@ function snapshotEncoding(filePath, content) {
|
|
|
30
30
|
return 'base64';
|
|
31
31
|
return 'utf8';
|
|
32
32
|
}
|
|
33
|
+
const MAX_SNAPSHOT_READ_BYTES = MAX_STORED_CONTENT_CHARS;
|
|
34
|
+
const SNAPSHOT_HASH_CHUNK_BYTES = 1024 * 1024;
|
|
35
|
+
function hashFileInChunks(absPath) {
|
|
36
|
+
const hash = createHash('sha256');
|
|
37
|
+
const chunk = Buffer.allocUnsafe(SNAPSHOT_HASH_CHUNK_BYTES);
|
|
38
|
+
const fd = openSync(absPath, 'r');
|
|
39
|
+
try {
|
|
40
|
+
for (;;) {
|
|
41
|
+
const read = readSync(fd, chunk, 0, chunk.length, null);
|
|
42
|
+
if (read <= 0)
|
|
43
|
+
break;
|
|
44
|
+
hash.update(chunk.subarray(0, read));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
closeSync(fd);
|
|
49
|
+
}
|
|
50
|
+
return `sha256:${hash.digest('hex')}`;
|
|
51
|
+
}
|
|
33
52
|
export function readFileEditSnapshot(rootDir, filePath) {
|
|
34
53
|
try {
|
|
35
54
|
const absPath = resolveProjectPath(rootDir, filePath);
|
|
36
55
|
if (!existsSync(absPath)) {
|
|
37
56
|
return { exists: false, content: null, contentEncoding: 'utf8', hash: null };
|
|
38
57
|
}
|
|
39
|
-
|
|
58
|
+
const stat = lstatSync(absPath);
|
|
59
|
+
if (stat.isSymbolicLink()) {
|
|
40
60
|
return {
|
|
41
61
|
exists: true,
|
|
42
62
|
content: null,
|
|
@@ -45,6 +65,17 @@ export function readFileEditSnapshot(rootDir, filePath) {
|
|
|
45
65
|
error: `Refusing to snapshot symbolic link: ${filePath}`,
|
|
46
66
|
};
|
|
47
67
|
}
|
|
68
|
+
if (stat.isFile() && stat.size > MAX_SNAPSHOT_READ_BYTES) {
|
|
69
|
+
return {
|
|
70
|
+
exists: true,
|
|
71
|
+
content: null,
|
|
72
|
+
contentEncoding: BINARY_ARTIFACT_EXTENSIONS.has(path.extname(filePath).toLowerCase())
|
|
73
|
+
? 'base64'
|
|
74
|
+
: 'utf8',
|
|
75
|
+
hash: hashFileInChunks(absPath),
|
|
76
|
+
error: `File is too large to snapshot (${stat.size} bytes).`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
48
79
|
const content = readFileSync(absPath);
|
|
49
80
|
const encoding = snapshotEncoding(filePath, content);
|
|
50
81
|
return {
|
package/dist/src/patcher.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from './colors.js';
|
|
2
|
-
import { chmodSync, closeSync, constants, existsSync, fchmodSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, unlinkSync, writeFileSync, } from 'fs';
|
|
2
|
+
import { chmodSync, closeSync, constants, existsSync, fchmodSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, unlinkSync, writeFileSync, } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createInterface } from 'readline';
|
|
5
5
|
import { runCommand } from './executor.js';
|
|
@@ -271,22 +271,61 @@ export function writeProjectFileBuffer(rootDir, filePath, content) {
|
|
|
271
271
|
}
|
|
272
272
|
return { absPath, changed: true };
|
|
273
273
|
}
|
|
274
|
+
const MAX_DELETED_CONTENT_BYTES = 64 * 1024;
|
|
275
|
+
const DELETED_BINARY_SNIFF_BYTES = 4096;
|
|
276
|
+
function headHasNulByte(absPath) {
|
|
277
|
+
const chunk = Buffer.allocUnsafe(DELETED_BINARY_SNIFF_BYTES);
|
|
278
|
+
let fd = null;
|
|
279
|
+
try {
|
|
280
|
+
fd = openSync(absPath, 'r');
|
|
281
|
+
const read = readSync(fd, chunk, 0, chunk.length, 0);
|
|
282
|
+
return read > 0 && chunk.subarray(0, read).includes(0);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
if (fd !== null)
|
|
289
|
+
closeSync(fd);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
274
292
|
export function deleteProjectFile(rootDir, filePath) {
|
|
275
293
|
const absPath = resolveProjectPath(rootDir, filePath);
|
|
276
294
|
if (!existsSync(absPath)) {
|
|
277
295
|
return { deleted: false, absPath };
|
|
278
296
|
}
|
|
297
|
+
let bytes;
|
|
279
298
|
let content;
|
|
299
|
+
let contentOmitted;
|
|
280
300
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
301
|
+
const stat = lstatSync(absPath);
|
|
302
|
+
if (!stat.isSymbolicLink()) {
|
|
303
|
+
bytes = stat.size;
|
|
304
|
+
if (stat.size > MAX_DELETED_CONTENT_BYTES) {
|
|
305
|
+
contentOmitted = headHasNulByte(absPath) ? 'binary' : 'too_large';
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const buffer = readFileSync(absPath);
|
|
309
|
+
if (buffer.includes(0))
|
|
310
|
+
contentOmitted = 'binary';
|
|
311
|
+
else
|
|
312
|
+
content = buffer.toString('utf-8');
|
|
313
|
+
}
|
|
283
314
|
}
|
|
284
315
|
}
|
|
285
316
|
catch {
|
|
317
|
+
bytes = undefined;
|
|
286
318
|
content = undefined;
|
|
319
|
+
contentOmitted = undefined;
|
|
287
320
|
}
|
|
288
321
|
unlinkSync(absPath);
|
|
289
|
-
return {
|
|
322
|
+
return {
|
|
323
|
+
deleted: true,
|
|
324
|
+
absPath,
|
|
325
|
+
...(bytes === undefined ? {} : { bytes }),
|
|
326
|
+
...(content === undefined ? {} : { content }),
|
|
327
|
+
...(contentOmitted === undefined ? {} : { contentOmitted }),
|
|
328
|
+
};
|
|
290
329
|
}
|
|
291
330
|
export function readProjectFile(rootDir, filePath) {
|
|
292
331
|
const absPath = resolveProjectPath(rootDir, filePath);
|
|
@@ -51,7 +51,11 @@ export async function deleteFile(context, args) {
|
|
|
51
51
|
changed: result.deleted,
|
|
52
52
|
deleted: result.deleted,
|
|
53
53
|
...(scratchPath ? { scratch: true } : {}),
|
|
54
|
-
|
|
54
|
+
...(result.bytes === undefined ? {} : { bytes: result.bytes }),
|
|
55
|
+
...(result.contentOmitted === undefined
|
|
56
|
+
? {}
|
|
57
|
+
: { contentOmitted: result.contentOmitted }),
|
|
58
|
+
...(result.content === undefined ? {} : { content: result.content }),
|
|
55
59
|
diagnostics: result.deleted && !scratchPath ? runShellDiagnostics(rootDir) : undefined,
|
|
56
60
|
};
|
|
57
61
|
}
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -527,11 +527,43 @@ function buildWriteFileDiff(content) {
|
|
|
527
527
|
return '';
|
|
528
528
|
return `@@ -0,0 +1,${lines.length} @@\n${lines.map((line) => `+${line}`).join('\n')}`;
|
|
529
529
|
}
|
|
530
|
+
const MAX_DELETE_PREVIEW_LINES = 400;
|
|
530
531
|
function buildDeleteFileDiff(content) {
|
|
531
532
|
const lines = splitDiffLines(String(content ?? ''));
|
|
532
|
-
if (lines.length === 0)
|
|
533
|
+
if (lines.length === 0) {
|
|
534
|
+
return { removed: 0, text: '' };
|
|
535
|
+
}
|
|
536
|
+
const shown = lines.slice(0, MAX_DELETE_PREVIEW_LINES);
|
|
537
|
+
const rows = [
|
|
538
|
+
`@@ -1,${lines.length} +0,0 @@`,
|
|
539
|
+
...shown.map((line) => `-${line}`),
|
|
540
|
+
];
|
|
541
|
+
const omitted = lines.length - shown.length;
|
|
542
|
+
if (omitted > 0) {
|
|
543
|
+
rows.push(`@@ ${omitted} more removed line(s) not shown @@`);
|
|
544
|
+
}
|
|
545
|
+
return { removed: lines.length, text: rows.join('\n') };
|
|
546
|
+
}
|
|
547
|
+
function deletedFileSummary(result) {
|
|
548
|
+
const bytes = typeof result?.bytes === 'number' ? result.bytes : null;
|
|
549
|
+
if (bytes === null) {
|
|
533
550
|
return '';
|
|
534
|
-
|
|
551
|
+
}
|
|
552
|
+
const reason = result?.contentOmitted === 'binary' ? ', binary' : '';
|
|
553
|
+
return ` (${formatFileSize(bytes)}${reason})`;
|
|
554
|
+
}
|
|
555
|
+
function formatFileSize(bytes) {
|
|
556
|
+
if (bytes < 1024) {
|
|
557
|
+
return `${bytes} B`;
|
|
558
|
+
}
|
|
559
|
+
const units = ['KiB', 'MiB', 'GiB', 'TiB'];
|
|
560
|
+
let value = bytes / 1024;
|
|
561
|
+
let unit = 0;
|
|
562
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
563
|
+
value /= 1024;
|
|
564
|
+
unit += 1;
|
|
565
|
+
}
|
|
566
|
+
return `${value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
535
567
|
}
|
|
536
568
|
function isFileChangeTool(name) {
|
|
537
569
|
return (name === 'patch_file' ||
|
|
@@ -659,9 +691,11 @@ function buildFileChangeEntry(event) {
|
|
|
659
691
|
};
|
|
660
692
|
}
|
|
661
693
|
const content = typeof result?.content === 'string' ? result.content : '';
|
|
662
|
-
const
|
|
663
|
-
const preview =
|
|
664
|
-
const summary = preview
|
|
694
|
+
const diff = buildDeleteFileDiff(content);
|
|
695
|
+
const preview = diff.text ? parseDiffPreview(diff.text) : undefined;
|
|
696
|
+
const summary = preview
|
|
697
|
+
? ` (+0 -${diff.removed})`
|
|
698
|
+
: deletedFileSummary(result);
|
|
665
699
|
return {
|
|
666
700
|
body: '',
|
|
667
701
|
diffPreview: preview,
|
|
@@ -2255,7 +2289,14 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2255
2289
|
}));
|
|
2256
2290
|
try {
|
|
2257
2291
|
const snapshot = await loadInteractiveSession(selected.id);
|
|
2258
|
-
|
|
2292
|
+
const requestedModelId = Number(snapshot.modelId);
|
|
2293
|
+
const knownModels = currentServerModels.models.some((model) => model.id === requestedModelId)
|
|
2294
|
+
? currentServerModels
|
|
2295
|
+
: await refreshServerModels();
|
|
2296
|
+
const { modelId: resolvedModelId, fellBack } = models.resolveResumedSessionModel(requestedModelId, knownModels);
|
|
2297
|
+
applySessionSnapshot(session, snapshot, {
|
|
2298
|
+
modelOverride: fellBack ? resolvedModelId : undefined,
|
|
2299
|
+
});
|
|
2259
2300
|
setBackgroundJobSession(session.sessionId);
|
|
2260
2301
|
setScratchSession(session.sessionId);
|
|
2261
2302
|
setImageStoreSession(session.sessionId);
|
|
@@ -2280,6 +2321,15 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2280
2321
|
kind: 'system',
|
|
2281
2322
|
title: 'Session',
|
|
2282
2323
|
},
|
|
2324
|
+
...(fellBack
|
|
2325
|
+
? [
|
|
2326
|
+
{
|
|
2327
|
+
body: `This session's model is no longer available — switched to ${formatModelLabel(resolvedModelId, knownModels.models)}. Conversation history preserved.`,
|
|
2328
|
+
kind: 'system',
|
|
2329
|
+
title: 'Model',
|
|
2330
|
+
},
|
|
2331
|
+
]
|
|
2332
|
+
: []),
|
|
2283
2333
|
...buildTranscriptFromSessionHistory(session.history),
|
|
2284
2334
|
]);
|
|
2285
2335
|
await remountTui();
|
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.38",
|
|
4
4
|
"description": "TheGitAI is an agentic AI coding tool for your terminal. It reads and searches your repository, writes and edits files, runs your tests, and verifies the change before handing it back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentic-ai",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
45
45
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
46
46
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
47
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
48
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
49
|
-
"@thegitai/tui-linux-arm64": "1.0.0-preview.
|
|
50
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
51
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
47
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.38",
|
|
48
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.38",
|
|
49
|
+
"@thegitai/tui-linux-arm64": "1.0.0-preview.38",
|
|
50
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.38",
|
|
51
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.38",
|
|
52
52
|
"@vscode/ripgrep": "1.18.0"
|
|
53
53
|
},
|
|
54
54
|
"repository": {
|