@thegitai/cli 1.0.0-preview.3 → 1.0.0-preview.30
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 +39 -6
- package/dist/bin/ai.js +142 -383
- package/dist/src/agent-mode.js +1 -6
- package/dist/src/api/auth.js +6 -4
- package/dist/src/api/browser-login.js +152 -37
- package/dist/src/api/chat.js +258 -38
- package/dist/src/api/contracts.js +55 -1
- package/dist/src/api/default-host.js +1 -0
- package/dist/src/api/http.js +69 -7
- package/dist/src/api/models.js +19 -10
- package/dist/src/background-jobs.js +2 -2
- package/dist/src/cli-args.js +19 -5
- package/dist/src/core/clipboard.js +7 -13
- package/dist/src/core/image-limits.js +56 -0
- package/dist/src/core/image-path-extractor.js +70 -3
- package/dist/src/core/session-image-store.js +199 -0
- package/dist/src/executor.js +25 -3
- package/dist/src/help-text.js +63 -16
- package/dist/src/permissions.js +243 -0
- package/dist/src/session-safety.js +0 -12
- package/dist/src/session-store.js +121 -20
- package/dist/src/session.js +14 -3
- package/dist/src/signin.js +58 -0
- package/dist/src/tool-executor.js +11 -46
- package/dist/src/tools/delete-file.js +15 -3
- package/dist/src/tools/index.js +13 -10
- package/dist/src/tools/patch-file.js +12 -26
- package/dist/src/tools/read-image-file.js +85 -0
- package/dist/src/tools/replace-document-text.js +28 -18
- package/dist/src/tools/restore-checkpoint.js +0 -1
- package/dist/src/tools/run-command.js +14 -71
- package/dist/src/tools/run-node-script.js +12 -81
- package/dist/src/tools/save-generated-image.js +120 -0
- package/dist/src/tools/str-replace.js +12 -26
- package/dist/src/tools/undo-edit.js +1 -6
- package/dist/src/tools/write-file.js +67 -11
- package/dist/src/turn-failure-marker.js +11 -0
- package/dist/src/ui/prompt-history-store.js +1 -1
- package/dist/src/ui/repl.js +610 -164
- package/dist/src/ui/tui/bridge.js +10 -0
- package/dist/src/ui/tui/build-frame.js +452 -115
- package/dist/src/ui/tui/markdown-render.js +81 -73
- package/dist/src/ui/tui/shell-input.js +206 -63
- package/dist/src/ui/tui/terminal-theme.js +28 -0
- package/dist/src/ui/tui/terminal-title.js +3 -0
- package/dist/src/ui/tui/terminal-writes.js +48 -0
- package/dist/src/ui/tui/text.js +158 -4
- package/dist/src/ui/tui/user-input.js +568 -0
- package/dist/src/utils.js +9 -0
- package/package.json +29 -6
- package/dist/src/markdown-renderer.js +0 -112
- package/dist/src/project-index.js +0 -221
- package/dist/src/tools/code-intel.js +0 -472
- package/dist/src/tools/find-symbol.js +0 -70
- package/dist/src/tools/hover-symbol.js +0 -95
- package/dist/src/tools/list-symbols.js +0 -55
- package/dist/src/tools/search-code.js +0 -37
- package/dist/src/tools/signature-help.js +0 -118
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import chalk from './colors.js';
|
|
2
|
-
function renderInline(text) {
|
|
3
|
-
const parts = String(text ?? '').split(/(`[^`]+`)/g);
|
|
4
|
-
return parts
|
|
5
|
-
.map((part) => part.startsWith('`') && part.endsWith('`') && part.length > 1
|
|
6
|
-
? chalk.cyan(part.slice(1, -1))
|
|
7
|
-
: part)
|
|
8
|
-
.join('');
|
|
9
|
-
}
|
|
10
|
-
function isTableSeparator(line) {
|
|
11
|
-
const cells = splitTableRow(line);
|
|
12
|
-
return (cells.length > 1 &&
|
|
13
|
-
cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim())));
|
|
14
|
-
}
|
|
15
|
-
function splitTableRow(line) {
|
|
16
|
-
const trimmed = String(line ?? '').trim();
|
|
17
|
-
if (!trimmed.includes('|'))
|
|
18
|
-
return [];
|
|
19
|
-
return trimmed
|
|
20
|
-
.replace(/^\|/, '')
|
|
21
|
-
.replace(/\|$/, '')
|
|
22
|
-
.split('|')
|
|
23
|
-
.map((cell) => cell.trim());
|
|
24
|
-
}
|
|
25
|
-
function tableWidth(text) {
|
|
26
|
-
return text.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
27
|
-
}
|
|
28
|
-
function padCell(text, width) {
|
|
29
|
-
return `${text}${' '.repeat(Math.max(0, width - tableWidth(text)))}`;
|
|
30
|
-
}
|
|
31
|
-
function renderTable(rows) {
|
|
32
|
-
if (rows.length < 2 || !isTableSeparator(rows[1].join('|')))
|
|
33
|
-
return [];
|
|
34
|
-
const headers = rows[0];
|
|
35
|
-
const body = rows.slice(2).filter((row) => row.length > 0);
|
|
36
|
-
const columnCount = Math.max(headers.length, ...body.map((row) => row.length));
|
|
37
|
-
const normalizedRows = [headers, ...body].map((row) => Array.from({ length: columnCount }, (_, index) => renderInline(row[index] ?? '')));
|
|
38
|
-
const widths = Array.from({ length: columnCount }, (_, index) => Math.max(...normalizedRows.map((row) => tableWidth(row[index] ?? '')), 3));
|
|
39
|
-
const border = `+${widths.map((width) => '-'.repeat(width + 2)).join('+')}+`;
|
|
40
|
-
const renderRow = (row) => `| ${row.map((cell, index) => padCell(cell, widths[index])).join(' | ')} |`;
|
|
41
|
-
return [
|
|
42
|
-
border,
|
|
43
|
-
renderRow(normalizedRows[0].map((cell) => chalk.bold(cell))),
|
|
44
|
-
border,
|
|
45
|
-
...normalizedRows.slice(1).map(renderRow),
|
|
46
|
-
border,
|
|
47
|
-
];
|
|
48
|
-
}
|
|
49
|
-
function readTableBlock(lines, startIndex) {
|
|
50
|
-
if (startIndex + 1 >= lines.length)
|
|
51
|
-
return null;
|
|
52
|
-
const header = splitTableRow(lines[startIndex]);
|
|
53
|
-
const separator = splitTableRow(lines[startIndex + 1]);
|
|
54
|
-
if (header.length < 2 || separator.length < 2 || !isTableSeparator(lines[startIndex + 1])) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
const rows = [header, separator];
|
|
58
|
-
let index = startIndex + 2;
|
|
59
|
-
while (index < lines.length) {
|
|
60
|
-
const row = splitTableRow(lines[index]);
|
|
61
|
-
if (row.length < 2)
|
|
62
|
-
break;
|
|
63
|
-
rows.push(row);
|
|
64
|
-
index += 1;
|
|
65
|
-
}
|
|
66
|
-
const rendered = renderTable(rows);
|
|
67
|
-
return rendered.length ? { rendered, nextIndex: index } : null;
|
|
68
|
-
}
|
|
69
|
-
export function renderMarkdownForTerminal(markdown) {
|
|
70
|
-
const lines = String(markdown ?? '').replace(/\r\n?/g, '\n').split('\n');
|
|
71
|
-
const output = [];
|
|
72
|
-
let inCodeBlock = false;
|
|
73
|
-
for (let index = 0; index < lines.length; index++) {
|
|
74
|
-
const line = lines[index];
|
|
75
|
-
if (/^\s*```/.test(line)) {
|
|
76
|
-
inCodeBlock = !inCodeBlock;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (inCodeBlock) {
|
|
80
|
-
output.push(chalk.dim(` ${line}`));
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const table = readTableBlock(lines, index);
|
|
84
|
-
if (table) {
|
|
85
|
-
output.push(...table.rendered);
|
|
86
|
-
index = table.nextIndex - 1;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const heading = line.match(/^\s{0,3}#{1,6}\s+(.+)$/);
|
|
90
|
-
if (heading) {
|
|
91
|
-
output.push(chalk.bold(renderInline(heading[1].trim())));
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const bullet = line.match(/^(\s*)[-*]\s+(.+)$/);
|
|
95
|
-
if (bullet) {
|
|
96
|
-
output.push(`${bullet[1]}- ${renderInline(bullet[2].trim())}`);
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
const numbered = line.match(/^(\s*)\d+[.)]\s+(.+)$/);
|
|
100
|
-
if (numbered) {
|
|
101
|
-
output.push(`${numbered[1]}- ${renderInline(numbered[2].trim())}`);
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
const quote = line.match(/^\s*>\s?(.+)$/);
|
|
105
|
-
if (quote) {
|
|
106
|
-
output.push(chalk.dim(`> ${renderInline(quote[1].trim())}`));
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
output.push(renderInline(line));
|
|
110
|
-
}
|
|
111
|
-
return output.join('\n').trimEnd();
|
|
112
|
-
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { existsSync, statSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { listProjectFiles, scanFiles, shouldIgnorePath, } from './scanner.js';
|
|
4
|
-
import { truncate } from './utils.js';
|
|
5
|
-
function normalizeProjectFilePath(rootDir, filePath) {
|
|
6
|
-
const resolvedRoot = path.resolve(rootDir);
|
|
7
|
-
const resolvedFile = path.resolve(resolvedRoot, filePath);
|
|
8
|
-
const relative = path.relative(resolvedRoot, resolvedFile);
|
|
9
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
return relative.replace(/\\/g, '/');
|
|
13
|
-
}
|
|
14
|
-
function getFileSignature(rootDir, relPath) {
|
|
15
|
-
try {
|
|
16
|
-
const stat = statSync(path.join(rootDir, relPath));
|
|
17
|
-
if (!stat.isFile()) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
return `${stat.size}:${stat.mtimeMs}`;
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function setChunksForFile(index, relPath, chunks) {
|
|
27
|
-
if (chunks.length === 0) {
|
|
28
|
-
index.chunksByFile.delete(relPath);
|
|
29
|
-
index.fileSignatures.delete(relPath);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
index.chunksByFile.set(relPath, chunks);
|
|
33
|
-
const signature = getFileSignature(index.rootDir, relPath);
|
|
34
|
-
if (signature) {
|
|
35
|
-
index.fileSignatures.set(relPath, signature);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function removeFile(index, relPath) {
|
|
39
|
-
index.chunksByFile.delete(relPath);
|
|
40
|
-
index.fileSignatures.delete(relPath);
|
|
41
|
-
}
|
|
42
|
-
async function initializeIndex(index) {
|
|
43
|
-
if (index.initialized) {
|
|
44
|
-
return Array.from(index.chunksByFile.values()).reduce((sum, chunks) => sum + chunks.length, 0);
|
|
45
|
-
}
|
|
46
|
-
const files = listProjectFiles(index.rootDir);
|
|
47
|
-
const chunks = await scanFiles(index.rootDir, files);
|
|
48
|
-
index.fileSignatures.clear();
|
|
49
|
-
index.chunksByFile.clear();
|
|
50
|
-
for (const filePath of files) {
|
|
51
|
-
const signature = getFileSignature(index.rootDir, filePath);
|
|
52
|
-
if (signature) {
|
|
53
|
-
index.fileSignatures.set(filePath, signature);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
for (const chunk of chunks) {
|
|
57
|
-
const current = index.chunksByFile.get(chunk.filePath) ?? [];
|
|
58
|
-
current.push(chunk);
|
|
59
|
-
index.chunksByFile.set(chunk.filePath, current);
|
|
60
|
-
}
|
|
61
|
-
index.initialized = true;
|
|
62
|
-
index.onStatus?.(`Local code index ready: ${index.fileSignatures.size.toLocaleString()} files.`);
|
|
63
|
-
return chunks.length;
|
|
64
|
-
}
|
|
65
|
-
function queryTerms(query) {
|
|
66
|
-
const parts = query
|
|
67
|
-
.toLowerCase()
|
|
68
|
-
.split(/[^a-z0-9_./-]+/i)
|
|
69
|
-
.map((part) => part.trim())
|
|
70
|
-
.filter(Boolean);
|
|
71
|
-
return [...new Set(parts)];
|
|
72
|
-
}
|
|
73
|
-
function countOccurrences(haystack, needle) {
|
|
74
|
-
if (!needle)
|
|
75
|
-
return 0;
|
|
76
|
-
let count = 0;
|
|
77
|
-
let cursor = 0;
|
|
78
|
-
while (cursor < haystack.length) {
|
|
79
|
-
const index = haystack.indexOf(needle, cursor);
|
|
80
|
-
if (index === -1)
|
|
81
|
-
break;
|
|
82
|
-
count += 1;
|
|
83
|
-
cursor = index + needle.length;
|
|
84
|
-
}
|
|
85
|
-
return count;
|
|
86
|
-
}
|
|
87
|
-
function scoreChunk(terms, chunk) {
|
|
88
|
-
const filePath = chunk.filePath.toLowerCase();
|
|
89
|
-
const content = chunk.content.toLowerCase();
|
|
90
|
-
let score = 0;
|
|
91
|
-
for (const term of terms) {
|
|
92
|
-
if (term.length < 2)
|
|
93
|
-
continue;
|
|
94
|
-
score += countOccurrences(filePath, term) * 3;
|
|
95
|
-
score += countOccurrences(content, term);
|
|
96
|
-
if (chunk.label?.toLowerCase().includes(term)) {
|
|
97
|
-
score += 2;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return score;
|
|
101
|
-
}
|
|
102
|
-
function flattenChunks(index) {
|
|
103
|
-
return Array.from(index.chunksByFile.values()).flat();
|
|
104
|
-
}
|
|
105
|
-
export function createIndex({ rootDir, onStatus = null, onContextLog = null, }) {
|
|
106
|
-
return {
|
|
107
|
-
rootDir: path.resolve(rootDir),
|
|
108
|
-
initialized: false,
|
|
109
|
-
fileSignatures: new Map(),
|
|
110
|
-
chunksByFile: new Map(),
|
|
111
|
-
onStatus,
|
|
112
|
-
onContextLog,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
export async function searchIndex(index, query, limit = 10) {
|
|
116
|
-
await initializeIndex(index);
|
|
117
|
-
const terms = queryTerms(query);
|
|
118
|
-
if (terms.length === 0) {
|
|
119
|
-
return { results: [], retrievalTokensUsed: 0 };
|
|
120
|
-
}
|
|
121
|
-
const scored = flattenChunks(index)
|
|
122
|
-
.map((chunk) => ({
|
|
123
|
-
...chunk,
|
|
124
|
-
score: scoreChunk(terms, chunk),
|
|
125
|
-
}))
|
|
126
|
-
.filter((chunk) => chunk.score > 0)
|
|
127
|
-
.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath))
|
|
128
|
-
.slice(0, Math.max(1, Math.min(limit, 20)));
|
|
129
|
-
if (scored.length) {
|
|
130
|
-
const visible = scored
|
|
131
|
-
.slice(0, 6)
|
|
132
|
-
.map((chunk) => `${chunk.filePath}:${chunk.startLine}-${chunk.endLine}`)
|
|
133
|
-
.join(', ');
|
|
134
|
-
index.onContextLog?.(`Local search hit: ${visible}`);
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
results: scored,
|
|
138
|
-
retrievalTokensUsed: 0,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
export function formatIndexResults(results) {
|
|
142
|
-
return results.map((chunk) => ({
|
|
143
|
-
...chunk,
|
|
144
|
-
content: truncate(chunk.content, 1200),
|
|
145
|
-
}));
|
|
146
|
-
}
|
|
147
|
-
export function listIndexFiles(index) {
|
|
148
|
-
return [...index.fileSignatures.keys()].sort();
|
|
149
|
-
}
|
|
150
|
-
export async function upsertIndexFile(index, filePath) {
|
|
151
|
-
if (!index.initialized) {
|
|
152
|
-
return { indexedChunks: 0, retrievalTokensUsed: 0 };
|
|
153
|
-
}
|
|
154
|
-
const relPath = normalizeProjectFilePath(index.rootDir, filePath);
|
|
155
|
-
if (!relPath || shouldIgnorePath(relPath)) {
|
|
156
|
-
return { indexedChunks: 0, retrievalTokensUsed: 0 };
|
|
157
|
-
}
|
|
158
|
-
const chunks = existsSync(path.join(index.rootDir, relPath))
|
|
159
|
-
? await scanFiles(index.rootDir, [relPath])
|
|
160
|
-
: [];
|
|
161
|
-
setChunksForFile(index, relPath, chunks);
|
|
162
|
-
return {
|
|
163
|
-
indexedChunks: chunks.length,
|
|
164
|
-
retrievalTokensUsed: 0,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
export async function removeIndexFile(index, filePath) {
|
|
168
|
-
const relPath = normalizeProjectFilePath(index.rootDir, filePath);
|
|
169
|
-
if (!relPath)
|
|
170
|
-
return;
|
|
171
|
-
removeFile(index, relPath);
|
|
172
|
-
}
|
|
173
|
-
export async function syncIndexFromDisk(index) {
|
|
174
|
-
if (!index.initialized) {
|
|
175
|
-
const indexedChunks = await initializeIndex(index);
|
|
176
|
-
return {
|
|
177
|
-
added: index.fileSignatures.size,
|
|
178
|
-
modified: 0,
|
|
179
|
-
removed: 0,
|
|
180
|
-
indexedChunks,
|
|
181
|
-
retrievalTokensUsed: 0,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
const currentFiles = new Set(listProjectFiles(index.rootDir));
|
|
185
|
-
let added = 0;
|
|
186
|
-
let modified = 0;
|
|
187
|
-
let removed = 0;
|
|
188
|
-
let indexedChunks = 0;
|
|
189
|
-
for (const existing of [...index.fileSignatures.keys()]) {
|
|
190
|
-
if (!currentFiles.has(existing)) {
|
|
191
|
-
removeFile(index, existing);
|
|
192
|
-
removed += 1;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
for (const relPath of currentFiles) {
|
|
196
|
-
const nextSignature = getFileSignature(index.rootDir, relPath);
|
|
197
|
-
if (!nextSignature)
|
|
198
|
-
continue;
|
|
199
|
-
const previousSignature = index.fileSignatures.get(relPath);
|
|
200
|
-
if (!previousSignature) {
|
|
201
|
-
const chunks = await scanFiles(index.rootDir, [relPath]);
|
|
202
|
-
setChunksForFile(index, relPath, chunks);
|
|
203
|
-
added += 1;
|
|
204
|
-
indexedChunks += chunks.length;
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
if (previousSignature !== nextSignature) {
|
|
208
|
-
const chunks = await scanFiles(index.rootDir, [relPath]);
|
|
209
|
-
setChunksForFile(index, relPath, chunks);
|
|
210
|
-
modified += 1;
|
|
211
|
-
indexedChunks += chunks.length;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
added,
|
|
216
|
-
modified,
|
|
217
|
-
removed,
|
|
218
|
-
indexedChunks,
|
|
219
|
-
retrievalTokensUsed: 0,
|
|
220
|
-
};
|
|
221
|
-
}
|