@polka-codes/cli 0.0.4 → 0.1.0
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/config.d.ts +37 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +87 -0
- package/dist/config.test.js.map +1 -0
- package/dist/handlers.d.ts +14 -0
- package/dist/handlers.js +239 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +23 -10
- package/dist/index.js.map +1 -1
- package/dist/listFiles.d.ts +11 -0
- package/dist/listFiles.js +100 -0
- package/dist/listFiles.js.map +1 -0
- package/dist/listFiles.test.d.ts +1 -0
- package/dist/listFiles.test.js +49 -0
- package/dist/listFiles.test.js.map +1 -0
- package/dist/replaceInFile.d.ts +1 -0
- package/dist/replaceInFile.js +85 -0
- package/dist/replaceInFile.js.map +1 -0
- package/dist/replaceInFile.test.d.ts +1 -0
- package/dist/replaceInFile.test.js +90 -0
- package/dist/replaceInFile.test.js.map +1 -0
- package/dist/runTask.d.ts +9 -0
- package/dist/runTask.js +107 -0
- package/dist/runTask.js.map +1 -0
- package/dist/utils/listFiles.d.ts +11 -0
- package/dist/utils/listFiles.js +100 -0
- package/dist/utils/listFiles.js.map +1 -0
- package/dist/utils/listFiles.test.d.ts +1 -0
- package/dist/utils/listFiles.test.js +49 -0
- package/dist/utils/listFiles.test.js.map +1 -0
- package/dist/utils/replaceInFile.d.ts +1 -0
- package/dist/utils/replaceInFile.js +85 -0
- package/dist/utils/replaceInFile.js.map +1 -0
- package/dist/utils/replaceInFile.test.d.ts +1 -0
- package/dist/utils/replaceInFile.test.js +90 -0
- package/dist/utils/replaceInFile.test.js.map +1 -0
- package/package.json +9 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'bun:test';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { listFiles } from './listFiles';
|
|
5
|
+
describe('listFiles', () => {
|
|
6
|
+
const testDir = join(__dirname, 'test-fixtures');
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
// Create test directory structure
|
|
9
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
10
|
+
await fs.writeFile(join(testDir, '.gitignore'), 'ignored.txt\nsubdir/ignored-too.txt');
|
|
11
|
+
await fs.writeFile(join(testDir, 'file1.txt'), '');
|
|
12
|
+
await fs.writeFile(join(testDir, 'file2.txt'), '');
|
|
13
|
+
await fs.writeFile(join(testDir, 'ignored.txt'), '');
|
|
14
|
+
// Create subdirectory
|
|
15
|
+
const subDir = join(testDir, 'subdir');
|
|
16
|
+
await fs.mkdir(subDir);
|
|
17
|
+
await fs.writeFile(join(subDir, 'file3.txt'), '');
|
|
18
|
+
await fs.writeFile(join(subDir, 'ignored-too.txt'), '');
|
|
19
|
+
});
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
// Clean up test directory
|
|
22
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
it('should list files in directory', async () => {
|
|
25
|
+
const [files] = await listFiles(testDir, false, 10, testDir);
|
|
26
|
+
expect(files).toEqual(['.gitignore', 'file1.txt', 'file2.txt']);
|
|
27
|
+
});
|
|
28
|
+
it('should list files recursively', async () => {
|
|
29
|
+
const [files] = await listFiles(testDir, true, 10, testDir);
|
|
30
|
+
expect(files).toEqual(['.gitignore', 'file1.txt', 'file2.txt', 'subdir/file3.txt']);
|
|
31
|
+
});
|
|
32
|
+
it('should respect maxCount', async () => {
|
|
33
|
+
const [files, limitReached] = await listFiles(testDir, true, 3, testDir);
|
|
34
|
+
expect(files.length).toBe(3);
|
|
35
|
+
expect(limitReached).toBe(true);
|
|
36
|
+
expect(files).toEqual(['.gitignore', 'file1.txt', 'file2.txt']);
|
|
37
|
+
});
|
|
38
|
+
it('should handle empty directory', async () => {
|
|
39
|
+
const emptyDir = join(testDir, 'empty');
|
|
40
|
+
await fs.mkdir(emptyDir);
|
|
41
|
+
const [files] = await listFiles(emptyDir, true, 10, emptyDir);
|
|
42
|
+
expect(files).toEqual([]);
|
|
43
|
+
await fs.rm(emptyDir, { recursive: true });
|
|
44
|
+
});
|
|
45
|
+
it('should handle non-existent directory', async () => {
|
|
46
|
+
expect(listFiles(join(testDir, 'nonexistent'), false, 10, testDir)).rejects.toThrow();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=listFiles.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listFiles.test.js","sourceRoot":"","sources":["../src/listFiles.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const replaceInFile: (fileContent: string, diff: string) => Promise<string>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* Example diff block format:
|
|
2
|
+
<<<<<<< SEARCH
|
|
3
|
+
// original text
|
|
4
|
+
=======
|
|
5
|
+
// replacement text
|
|
6
|
+
>>>>>>> REPLACE
|
|
7
|
+
*/
|
|
8
|
+
export const replaceInFile = async (fileContent, diff) => {
|
|
9
|
+
// Regex to match blocks of the form:
|
|
10
|
+
// <<<<<<< SEARCH
|
|
11
|
+
// (some lines)
|
|
12
|
+
// =======
|
|
13
|
+
// (some lines)
|
|
14
|
+
// >>>>>>> REPLACE
|
|
15
|
+
const blockPattern = /<<<<<+ SEARCH\s*\r?\n([\s\S]*?)\r?\n=======[ \t]*\r?\n([\s\S]*?)\r?\n?>>>>>+ REPLACE/g;
|
|
16
|
+
// Parse diff blocks
|
|
17
|
+
const blocks = [];
|
|
18
|
+
for (let match = blockPattern.exec(diff); match !== null; match = blockPattern.exec(diff)) {
|
|
19
|
+
blocks.push({ search: match[1], replace: match[2] });
|
|
20
|
+
}
|
|
21
|
+
if (blocks.length === 0) {
|
|
22
|
+
throw new Error('No valid diff blocks found.');
|
|
23
|
+
}
|
|
24
|
+
// Helper: try to find the search text in fileContent with progressive relaxation
|
|
25
|
+
const findAndReplace = (content, search, replace) => {
|
|
26
|
+
// 1) Direct exact match
|
|
27
|
+
let index = content.indexOf(search);
|
|
28
|
+
if (index !== -1) {
|
|
29
|
+
return content.slice(0, index) + replace + content.slice(index + search.length);
|
|
30
|
+
}
|
|
31
|
+
// 2) Trim leading/ending whitespace in search and content
|
|
32
|
+
const trimmedSearch = search.trim();
|
|
33
|
+
const trimmedContent = content.trim();
|
|
34
|
+
const offset = content.indexOf(trimmedContent); // to restore original indexing if found
|
|
35
|
+
index = trimmedContent.indexOf(trimmedSearch);
|
|
36
|
+
if (index !== -1) {
|
|
37
|
+
// compute correct absolute index in the original content
|
|
38
|
+
const absoluteIndex = offset + index;
|
|
39
|
+
return content.slice(0, absoluteIndex) + replace + content.slice(absoluteIndex + trimmedSearch.length);
|
|
40
|
+
}
|
|
41
|
+
// 3) Whitespace-agnostic match:
|
|
42
|
+
// Replace all consecutive whitespace in search and content with a single marker
|
|
43
|
+
// to see if there's a match ignoring whitespace diffs
|
|
44
|
+
const normalizedSearch = trimmedSearch.replace(/\s+/g, ' ');
|
|
45
|
+
const normalizedContent = trimmedContent.replace(/\s+/g, ' ');
|
|
46
|
+
index = normalizedContent.indexOf(normalizedSearch);
|
|
47
|
+
if (index !== -1) {
|
|
48
|
+
// Find actual location in the original content. We do a rough approach here:
|
|
49
|
+
// We know the substring's "normalized" start is at 'index' in normalizedContent.
|
|
50
|
+
// A simple way is to walk through the original trimmedContent to find where that occurs.
|
|
51
|
+
// For brevity, we'll do a naive re-scan and hope it's correct in typical cases.
|
|
52
|
+
let runningIndex = 0;
|
|
53
|
+
let actualPos = offset;
|
|
54
|
+
for (const segment of trimmedSearch.replace(/\s+/g, ' ').split(' ')) {
|
|
55
|
+
const segIndex = content.indexOf(segment, actualPos);
|
|
56
|
+
if (segIndex === -1) {
|
|
57
|
+
break; // mismatch, won't happen if we truly found it
|
|
58
|
+
}
|
|
59
|
+
if (runningIndex === 0) {
|
|
60
|
+
// First segment helps define the start
|
|
61
|
+
actualPos = segIndex;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Move just after the segment to keep scanning
|
|
65
|
+
actualPos = segIndex + segment.length;
|
|
66
|
+
}
|
|
67
|
+
runningIndex++;
|
|
68
|
+
}
|
|
69
|
+
// By the time we’re done, actualPos should be the end of the matched substring.
|
|
70
|
+
// We do a length calc for the final replacement index:
|
|
71
|
+
// but we need the total length of trimmedSearch minus whitespace. We'll reconstruct:
|
|
72
|
+
const strippedSearch = trimmedSearch.replace(/\s+/g, '');
|
|
73
|
+
const endPos = actualPos; // The end of the final segment
|
|
74
|
+
const startPos = endPos - strippedSearch.length;
|
|
75
|
+
return content.slice(0, startPos) + replace + content.slice(endPos);
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Could not find the following text in file:\n${search}`);
|
|
78
|
+
};
|
|
79
|
+
let updatedFile = fileContent;
|
|
80
|
+
for (const { search, replace } of blocks) {
|
|
81
|
+
updatedFile = findAndReplace(updatedFile, search, replace);
|
|
82
|
+
}
|
|
83
|
+
return updatedFile;
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=replaceInFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replaceInFile.js","sourceRoot":"","sources":["../src/replaceInFile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { replaceInFile } from './replaceInFile';
|
|
3
|
+
describe('replaceInFile', () => {
|
|
4
|
+
it('should perform basic single replacement', async () => {
|
|
5
|
+
const content = `line1
|
|
6
|
+
line2
|
|
7
|
+
line3`;
|
|
8
|
+
const diff = `<<<<<<< SEARCH
|
|
9
|
+
line2
|
|
10
|
+
=======
|
|
11
|
+
new line2
|
|
12
|
+
>>>>>>> REPLACE`;
|
|
13
|
+
const result = await replaceInFile(content, diff);
|
|
14
|
+
expect(result).toBe(`line1
|
|
15
|
+
new line2
|
|
16
|
+
line3`);
|
|
17
|
+
});
|
|
18
|
+
it('should handle multiple replacements', async () => {
|
|
19
|
+
const content = `line1
|
|
20
|
+
line2
|
|
21
|
+
line3
|
|
22
|
+
line2`;
|
|
23
|
+
const diff = `<<<<<<< SEARCH
|
|
24
|
+
line2
|
|
25
|
+
=======
|
|
26
|
+
new line2
|
|
27
|
+
>>>>>>> REPLACE
|
|
28
|
+
<<<<<<< SEARCH
|
|
29
|
+
line3
|
|
30
|
+
=======
|
|
31
|
+
new line3
|
|
32
|
+
>>>>>>> REPLACE`;
|
|
33
|
+
const result = await replaceInFile(content, diff);
|
|
34
|
+
expect(result).toBe(`line1
|
|
35
|
+
new line2
|
|
36
|
+
new line3
|
|
37
|
+
line2`);
|
|
38
|
+
});
|
|
39
|
+
it('should handle whitespace variations', async () => {
|
|
40
|
+
const content = `line1
|
|
41
|
+
line2
|
|
42
|
+
line3`;
|
|
43
|
+
const diff = `<<<<<<< SEARCH
|
|
44
|
+
line2
|
|
45
|
+
=======
|
|
46
|
+
new line2
|
|
47
|
+
>>>>>>> REPLACE`;
|
|
48
|
+
const result = await replaceInFile(content, diff);
|
|
49
|
+
expect(result).toBe(`line1
|
|
50
|
+
new line2
|
|
51
|
+
line3`);
|
|
52
|
+
});
|
|
53
|
+
it('should throw error when no blocks found', async () => {
|
|
54
|
+
const content = `line1
|
|
55
|
+
line2`;
|
|
56
|
+
const diff = 'invalid format';
|
|
57
|
+
expect(replaceInFile(content, diff)).rejects.toThrow('No valid diff blocks found');
|
|
58
|
+
});
|
|
59
|
+
it('should throw error when search text not found', async () => {
|
|
60
|
+
const content = `line1
|
|
61
|
+
line2`;
|
|
62
|
+
const diff = `<<<<<<< SEARCH
|
|
63
|
+
line3
|
|
64
|
+
=======
|
|
65
|
+
new line3
|
|
66
|
+
>>>>>>> REPLACE`;
|
|
67
|
+
expect(replaceInFile(content, diff)).rejects.toThrow('Could not find the following text in file');
|
|
68
|
+
});
|
|
69
|
+
it('should handle empty file', async () => {
|
|
70
|
+
const content = '';
|
|
71
|
+
const diff = `<<<<<<< SEARCH
|
|
72
|
+
line1
|
|
73
|
+
=======
|
|
74
|
+
new line1
|
|
75
|
+
>>>>>>> REPLACE`;
|
|
76
|
+
expect(replaceInFile(content, diff)).rejects.toThrow('Could not find the following text in file');
|
|
77
|
+
});
|
|
78
|
+
it('should handle empty replacement', async () => {
|
|
79
|
+
const content = `line1
|
|
80
|
+
line2`;
|
|
81
|
+
const diff = `<<<<<<< SEARCH
|
|
82
|
+
line2
|
|
83
|
+
=======
|
|
84
|
+
>>>>>>> REPLACE`;
|
|
85
|
+
const result = await replaceInFile(content, diff);
|
|
86
|
+
expect(result).toBe(`line1
|
|
87
|
+
`);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=replaceInFile.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replaceInFile.test.js","sourceRoot":"","sources":["../src/replaceInFile.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,UAAU,CAAA;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE/C,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,OAAO,GAAG;;MAEd,CAAA;QACF,MAAM,IAAI,GAAG;;;;gBAID,CAAA;QAEZ,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;;MAElB,CAAC,CAAA;IACL,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,OAAO,GAAG;;;MAGd,CAAA;QACF,MAAM,IAAI,GAAG;;;;;;;;;gBASD,CAAA;QAEZ,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;;;MAGlB,CAAC,CAAA;IACL,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,OAAO,GAAG;;MAEd,CAAA;QACF,MAAM,IAAI,GAAG;;;;gBAID,CAAA;QAEZ,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;;MAElB,CAAC,CAAA;IACL,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,OAAO,GAAG;MACd,CAAA;QACF,MAAM,IAAI,GAAG,gBAAgB,CAAA;QAE7B,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAA;IACpF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,OAAO,GAAG;MACd,CAAA;QACF,MAAM,IAAI,GAAG;;;;gBAID,CAAA;QAEZ,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAA;IACnG,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,OAAO,GAAG,EAAE,CAAA;QAClB,MAAM,IAAI,GAAG;;;;gBAID,CAAA;QAEZ,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAA;IACnG,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,OAAO,GAAG;MACd,CAAA;QACF,MAAM,IAAI,GAAG;;;gBAGD,CAAA;QAEZ,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;CACvB,CAAC,CAAA;IACA,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/dist/runTask.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { CoderAgent, createService, createServiceLogger } from '@polka-codes/core';
|
|
3
|
+
import { getToolHandler } from './handlers';
|
|
4
|
+
import { listFiles } from './utils/listFiles';
|
|
5
|
+
const logger = createServiceLogger('cli/runTask');
|
|
6
|
+
export const runTask = async (options) => {
|
|
7
|
+
logger.debug(options, 'Running task');
|
|
8
|
+
const { task, provider, model, apiKey, config } = options;
|
|
9
|
+
// Create service with the configured options
|
|
10
|
+
const service = createService(provider, {
|
|
11
|
+
apiKey,
|
|
12
|
+
modelId: model,
|
|
13
|
+
});
|
|
14
|
+
let rules = config.rules;
|
|
15
|
+
if (typeof rules === 'string') {
|
|
16
|
+
rules = [rules];
|
|
17
|
+
}
|
|
18
|
+
const agent = new CoderAgent({
|
|
19
|
+
ai: service,
|
|
20
|
+
os: os.platform(),
|
|
21
|
+
customInstructions: rules,
|
|
22
|
+
commands: config.commands,
|
|
23
|
+
toolHandler: getToolHandler({
|
|
24
|
+
executeCommand: {
|
|
25
|
+
async shouldExecute(command) {
|
|
26
|
+
// TODO: command whitelist
|
|
27
|
+
return true;
|
|
28
|
+
},
|
|
29
|
+
async shouldApprove(command) {
|
|
30
|
+
// TODO: config for auto approve
|
|
31
|
+
return true;
|
|
32
|
+
},
|
|
33
|
+
commandStarted(command, child) {
|
|
34
|
+
console.log(`$ >>>> $ ${command}`);
|
|
35
|
+
child.stdout.on('data', (data) => {
|
|
36
|
+
process.stdout.write(data.toString());
|
|
37
|
+
});
|
|
38
|
+
child.stderr.on('data', (data) => {
|
|
39
|
+
process.stderr.write(data.toString());
|
|
40
|
+
});
|
|
41
|
+
child.on('close', (code) => {
|
|
42
|
+
console.log(`$ <<<< $ Command exited with code: ${code}`);
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
const cwd = process.cwd();
|
|
49
|
+
const [fileList, limited] = await listFiles(cwd, true, 100, cwd);
|
|
50
|
+
const fileContext = `<files>
|
|
51
|
+
${fileList.join('\n')}${limited ? '\n<files_truncated>true</files_truncated>' : ''}
|
|
52
|
+
</files>`;
|
|
53
|
+
const usage = {
|
|
54
|
+
inputTokens: 0,
|
|
55
|
+
outputTokens: 0,
|
|
56
|
+
cacheWriteTokens: 0,
|
|
57
|
+
cacheReadTokens: 0,
|
|
58
|
+
totalCost: 0,
|
|
59
|
+
};
|
|
60
|
+
await agent.startTask({
|
|
61
|
+
task,
|
|
62
|
+
context: `<now_date>${new Date().toISOString()}</now_date>${fileContext}`,
|
|
63
|
+
maxIterations: 10,
|
|
64
|
+
callback: (event) => {
|
|
65
|
+
if (event.kind === 'usage') {
|
|
66
|
+
usage.inputTokens += event.info.inputTokens;
|
|
67
|
+
usage.outputTokens += event.info.outputTokens;
|
|
68
|
+
usage.cacheWriteTokens += event.info.cacheWriteTokens ?? 0;
|
|
69
|
+
usage.cacheReadTokens += event.info.cacheReadTokens ?? 0;
|
|
70
|
+
usage.totalCost += event.info.totalCost ?? 0;
|
|
71
|
+
}
|
|
72
|
+
if (event.kind === 'start_request') {
|
|
73
|
+
console.log('>>>>');
|
|
74
|
+
const { userContent } = event;
|
|
75
|
+
if (userContent) {
|
|
76
|
+
for (const content of userContent) {
|
|
77
|
+
console.log(content.type === 'text' ? content.text : '');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
console.log('====');
|
|
81
|
+
}
|
|
82
|
+
if (event.newText) {
|
|
83
|
+
process.stdout.write(event.newText);
|
|
84
|
+
}
|
|
85
|
+
if (event.kind === 'end_request') {
|
|
86
|
+
process.stdout.write('\n');
|
|
87
|
+
console.log('<<<<');
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
if (!usage.totalCost) {
|
|
92
|
+
// we need to calculate the total cost
|
|
93
|
+
const modelInfo = agent.model.info;
|
|
94
|
+
const inputCost = (modelInfo.inputPrice ?? 0) * usage.inputTokens;
|
|
95
|
+
const outputCost = (modelInfo.outputPrice ?? 0) * usage.outputTokens;
|
|
96
|
+
const cacheReadCost = (modelInfo.cacheReadsPrice ?? 0) * usage.cacheReadTokens;
|
|
97
|
+
const cacheWriteCost = (modelInfo.cacheWritesPrice ?? 0) * usage.cacheWriteTokens;
|
|
98
|
+
usage.totalCost = (inputCost + outputCost + cacheReadCost + cacheWriteCost) / 1_000_000;
|
|
99
|
+
}
|
|
100
|
+
console.log('Usages:');
|
|
101
|
+
console.log(`Input tokens: ${usage.inputTokens}`);
|
|
102
|
+
console.log(`Output tokens: ${usage.outputTokens}`);
|
|
103
|
+
console.log(`Cache read tokens: ${usage.cacheReadTokens}`);
|
|
104
|
+
console.log(`Cache write tokens: ${usage.cacheWriteTokens}`);
|
|
105
|
+
console.log(`Total cost: ${usage.totalCost}`);
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=runTask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runTask.js","sourceRoot":"","sources":["../src/runTask.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lists files under `dirPath` in BFS order, respecting:
|
|
3
|
+
* - A default set of ignores
|
|
4
|
+
* - A root .gitignore under `cwd`
|
|
5
|
+
* - Any .gitignore files in child directories (merged as we go)
|
|
6
|
+
*
|
|
7
|
+
* Returns `[files, limitReached]`:
|
|
8
|
+
* - `files` is the array of file paths (relative to `cwd`)
|
|
9
|
+
* - `limitReached` is `true` if `maxCount` was hit, otherwise `false`
|
|
10
|
+
*/
|
|
11
|
+
export declare function listFiles(dirPath: string, recursive: boolean, maxCount: number, cwd: string): Promise<[string[], boolean]>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { join, relative, resolve } from 'node:path';
|
|
3
|
+
import ignore from 'ignore';
|
|
4
|
+
/** Default patterns commonly ignored in projects of various languages. */
|
|
5
|
+
const DEFAULT_IGNORES = [
|
|
6
|
+
'__pycache__',
|
|
7
|
+
'.DS_Store',
|
|
8
|
+
'.env',
|
|
9
|
+
'.git',
|
|
10
|
+
'.idea',
|
|
11
|
+
'.svn',
|
|
12
|
+
'.temp',
|
|
13
|
+
'.vscode',
|
|
14
|
+
'coverage',
|
|
15
|
+
'dist',
|
|
16
|
+
'node_modules',
|
|
17
|
+
'out',
|
|
18
|
+
'Thumbs.db',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Reads a `.gitignore` file in `dirPath` (if it exists) and appends its lines
|
|
22
|
+
* to the `basePatterns`. Returns a new array without mutating the original.
|
|
23
|
+
*/
|
|
24
|
+
async function extendPatterns(basePatterns, dirPath) {
|
|
25
|
+
try {
|
|
26
|
+
const gitignorePath = join(dirPath, '.gitignore');
|
|
27
|
+
const content = await fs.readFile(gitignorePath, 'utf8');
|
|
28
|
+
const lines = content.split(/\r?\n/).filter(Boolean);
|
|
29
|
+
return [...basePatterns, ...lines];
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// No .gitignore or unreadable
|
|
33
|
+
return basePatterns;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Creates an `ignore` instance from the given patterns. */
|
|
37
|
+
function createIgnore(patterns) {
|
|
38
|
+
return ignore().add(patterns);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Lists files under `dirPath` in BFS order, respecting:
|
|
42
|
+
* - A default set of ignores
|
|
43
|
+
* - A root .gitignore under `cwd`
|
|
44
|
+
* - Any .gitignore files in child directories (merged as we go)
|
|
45
|
+
*
|
|
46
|
+
* Returns `[files, limitReached]`:
|
|
47
|
+
* - `files` is the array of file paths (relative to `cwd`)
|
|
48
|
+
* - `limitReached` is `true` if `maxCount` was hit, otherwise `false`
|
|
49
|
+
*/
|
|
50
|
+
export async function listFiles(dirPath, recursive, maxCount, cwd) {
|
|
51
|
+
// Merge default ignores with root .gitignore (if found)
|
|
52
|
+
let rootPatterns = [...DEFAULT_IGNORES];
|
|
53
|
+
try {
|
|
54
|
+
const rootGitignore = await fs.readFile(join(cwd, '.gitignore'), 'utf8');
|
|
55
|
+
const lines = rootGitignore.split(/\r?\n/).filter(Boolean);
|
|
56
|
+
rootPatterns = [...rootPatterns, ...lines];
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// No .gitignore at root or unreadable; ignore silently
|
|
60
|
+
}
|
|
61
|
+
// Final results (relative to `cwd`) and indicator if we reached the limit
|
|
62
|
+
const results = [];
|
|
63
|
+
// BFS queue
|
|
64
|
+
// Each entry holds the directory path and the patterns inherited from its parent
|
|
65
|
+
const queue = [{ path: resolve(dirPath), patterns: rootPatterns }];
|
|
66
|
+
// Perform BFS until queue is empty or maxCount is reached
|
|
67
|
+
while (queue.length > 0) {
|
|
68
|
+
// biome-ignore lint/style/noNonNullAssertion: checked above
|
|
69
|
+
const { path: currentPath, patterns: parentPatterns } = queue.shift();
|
|
70
|
+
// Merge parent's patterns with local .gitignore
|
|
71
|
+
const mergedPatterns = await extendPatterns(parentPatterns, currentPath);
|
|
72
|
+
const folderIg = createIgnore(mergedPatterns);
|
|
73
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const fullPath = join(currentPath, entry.name);
|
|
76
|
+
// Convert full path to something relative to `cwd`
|
|
77
|
+
const relPath = relative(cwd, fullPath).replace(/\\/g, '/');
|
|
78
|
+
if (folderIg.ignores(relPath)) {
|
|
79
|
+
continue; // Skip ignored entries
|
|
80
|
+
}
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
if (recursive) {
|
|
83
|
+
queue.push({ path: fullPath, patterns: mergedPatterns });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
results.push(relPath);
|
|
88
|
+
if (results.length >= maxCount) {
|
|
89
|
+
results.sort();
|
|
90
|
+
// Stop searching as soon as we reach maxCount
|
|
91
|
+
return [results, true];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
results.sort();
|
|
97
|
+
// If we exhaust the BFS queue, we did not reach maxCount
|
|
98
|
+
return [results, false];
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=listFiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listFiles.js","sourceRoot":"","sources":["../../src/utils/listFiles.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'bun:test';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { listFiles } from './listFiles';
|
|
5
|
+
describe('listFiles', () => {
|
|
6
|
+
const testDir = join(__dirname, 'test-fixtures');
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
// Create test directory structure
|
|
9
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
10
|
+
await fs.writeFile(join(testDir, '.gitignore'), 'ignored.txt\nsubdir/ignored-too.txt');
|
|
11
|
+
await fs.writeFile(join(testDir, 'file1.txt'), '');
|
|
12
|
+
await fs.writeFile(join(testDir, 'file2.txt'), '');
|
|
13
|
+
await fs.writeFile(join(testDir, 'ignored.txt'), '');
|
|
14
|
+
// Create subdirectory
|
|
15
|
+
const subDir = join(testDir, 'subdir');
|
|
16
|
+
await fs.mkdir(subDir);
|
|
17
|
+
await fs.writeFile(join(subDir, 'file3.txt'), '');
|
|
18
|
+
await fs.writeFile(join(subDir, 'ignored-too.txt'), '');
|
|
19
|
+
});
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
// Clean up test directory
|
|
22
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
it('should list files in directory', async () => {
|
|
25
|
+
const [files] = await listFiles(testDir, false, 10, testDir);
|
|
26
|
+
expect(files).toEqual(['.gitignore', 'file1.txt', 'file2.txt']);
|
|
27
|
+
});
|
|
28
|
+
it('should list files recursively', async () => {
|
|
29
|
+
const [files] = await listFiles(testDir, true, 10, testDir);
|
|
30
|
+
expect(files).toEqual(['.gitignore', 'file1.txt', 'file2.txt', 'subdir/file3.txt']);
|
|
31
|
+
});
|
|
32
|
+
it('should respect maxCount', async () => {
|
|
33
|
+
const [files, limitReached] = await listFiles(testDir, true, 3, testDir);
|
|
34
|
+
expect(files.length).toBe(3);
|
|
35
|
+
expect(limitReached).toBe(true);
|
|
36
|
+
expect(files).toEqual(['.gitignore', 'file1.txt', 'file2.txt']);
|
|
37
|
+
});
|
|
38
|
+
it('should handle empty directory', async () => {
|
|
39
|
+
const emptyDir = join(testDir, 'empty');
|
|
40
|
+
await fs.mkdir(emptyDir);
|
|
41
|
+
const [files] = await listFiles(emptyDir, true, 10, emptyDir);
|
|
42
|
+
expect(files).toEqual([]);
|
|
43
|
+
await fs.rm(emptyDir, { recursive: true });
|
|
44
|
+
});
|
|
45
|
+
it('should handle non-existent directory', async () => {
|
|
46
|
+
expect(listFiles(join(testDir, 'nonexistent'), false, 10, testDir)).rejects.toThrow();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=listFiles.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listFiles.test.js","sourceRoot":"","sources":["../../src/utils/listFiles.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const replaceInFile: (fileContent: string, diff: string) => Promise<string>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* Example diff block format:
|
|
2
|
+
<<<<<<< SEARCH
|
|
3
|
+
// original text
|
|
4
|
+
=======
|
|
5
|
+
// replacement text
|
|
6
|
+
>>>>>>> REPLACE
|
|
7
|
+
*/
|
|
8
|
+
export const replaceInFile = async (fileContent, diff) => {
|
|
9
|
+
// Regex to match blocks of the form:
|
|
10
|
+
// <<<<<<< SEARCH
|
|
11
|
+
// (some lines)
|
|
12
|
+
// =======
|
|
13
|
+
// (some lines)
|
|
14
|
+
// >>>>>>> REPLACE
|
|
15
|
+
const blockPattern = /<<<<<+ SEARCH\s*\r?\n([\s\S]*?)\r?\n=======[ \t]*\r?\n([\s\S]*?)\r?\n?>>>>>+ REPLACE/g;
|
|
16
|
+
// Parse diff blocks
|
|
17
|
+
const blocks = [];
|
|
18
|
+
for (let match = blockPattern.exec(diff); match !== null; match = blockPattern.exec(diff)) {
|
|
19
|
+
blocks.push({ search: match[1], replace: match[2] });
|
|
20
|
+
}
|
|
21
|
+
if (blocks.length === 0) {
|
|
22
|
+
throw new Error('No valid diff blocks found.');
|
|
23
|
+
}
|
|
24
|
+
// Helper: try to find the search text in fileContent with progressive relaxation
|
|
25
|
+
const findAndReplace = (content, search, replace) => {
|
|
26
|
+
// 1) Direct exact match
|
|
27
|
+
let index = content.indexOf(search);
|
|
28
|
+
if (index !== -1) {
|
|
29
|
+
return content.slice(0, index) + replace + content.slice(index + search.length);
|
|
30
|
+
}
|
|
31
|
+
// 2) Trim leading/ending whitespace in search and content
|
|
32
|
+
const trimmedSearch = search.trim();
|
|
33
|
+
const trimmedContent = content.trim();
|
|
34
|
+
const offset = content.indexOf(trimmedContent); // to restore original indexing if found
|
|
35
|
+
index = trimmedContent.indexOf(trimmedSearch);
|
|
36
|
+
if (index !== -1) {
|
|
37
|
+
// compute correct absolute index in the original content
|
|
38
|
+
const absoluteIndex = offset + index;
|
|
39
|
+
return content.slice(0, absoluteIndex) + replace + content.slice(absoluteIndex + trimmedSearch.length);
|
|
40
|
+
}
|
|
41
|
+
// 3) Whitespace-agnostic match:
|
|
42
|
+
// Replace all consecutive whitespace in search and content with a single marker
|
|
43
|
+
// to see if there's a match ignoring whitespace diffs
|
|
44
|
+
const normalizedSearch = trimmedSearch.replace(/\s+/g, ' ');
|
|
45
|
+
const normalizedContent = trimmedContent.replace(/\s+/g, ' ');
|
|
46
|
+
index = normalizedContent.indexOf(normalizedSearch);
|
|
47
|
+
if (index !== -1) {
|
|
48
|
+
// Find actual location in the original content. We do a rough approach here:
|
|
49
|
+
// We know the substring's "normalized" start is at 'index' in normalizedContent.
|
|
50
|
+
// A simple way is to walk through the original trimmedContent to find where that occurs.
|
|
51
|
+
// For brevity, we'll do a naive re-scan and hope it's correct in typical cases.
|
|
52
|
+
let runningIndex = 0;
|
|
53
|
+
let actualPos = offset;
|
|
54
|
+
for (const segment of trimmedSearch.replace(/\s+/g, ' ').split(' ')) {
|
|
55
|
+
const segIndex = content.indexOf(segment, actualPos);
|
|
56
|
+
if (segIndex === -1) {
|
|
57
|
+
break; // mismatch, won't happen if we truly found it
|
|
58
|
+
}
|
|
59
|
+
if (runningIndex === 0) {
|
|
60
|
+
// First segment helps define the start
|
|
61
|
+
actualPos = segIndex;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Move just after the segment to keep scanning
|
|
65
|
+
actualPos = segIndex + segment.length;
|
|
66
|
+
}
|
|
67
|
+
runningIndex++;
|
|
68
|
+
}
|
|
69
|
+
// By the time we’re done, actualPos should be the end of the matched substring.
|
|
70
|
+
// We do a length calc for the final replacement index:
|
|
71
|
+
// but we need the total length of trimmedSearch minus whitespace. We'll reconstruct:
|
|
72
|
+
const strippedSearch = trimmedSearch.replace(/\s+/g, '');
|
|
73
|
+
const endPos = actualPos; // The end of the final segment
|
|
74
|
+
const startPos = endPos - strippedSearch.length;
|
|
75
|
+
return content.slice(0, startPos) + replace + content.slice(endPos);
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Could not find the following text in file:\n${search}`);
|
|
78
|
+
};
|
|
79
|
+
let updatedFile = fileContent;
|
|
80
|
+
for (const { search, replace } of blocks) {
|
|
81
|
+
updatedFile = findAndReplace(updatedFile, search, replace);
|
|
82
|
+
}
|
|
83
|
+
return updatedFile;
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=replaceInFile.js.map
|