cloving 0.3.6 → 0.3.7
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/index.js
CHANGED
|
@@ -71,6 +71,7 @@ generate
|
|
|
71
71
|
.command('shell')
|
|
72
72
|
.alias('sh')
|
|
73
73
|
.description('Generate a shell command based on a prompt')
|
|
74
|
+
.option('-e, --exec', 'Automatically execute the generated shell command')
|
|
74
75
|
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
75
76
|
.option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5-sonnet-20240620)')
|
|
76
77
|
.option('-p, --prompt <prompt>', 'Specify the prompt to use')
|
|
@@ -38,9 +38,9 @@ class BlockManager extends events_1.EventEmitter {
|
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
40
|
// Start of code block
|
|
41
|
-
this.emit('startGeneratingCode');
|
|
42
41
|
this.emitBuffer(content.slice(0, markerIndex));
|
|
43
42
|
this.codeBuffer = content.slice(markerIndex);
|
|
43
|
+
this.emit('startGeneratingCode');
|
|
44
44
|
this.isBufferingCode = true;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -65,7 +65,7 @@ class BlockManager extends events_1.EventEmitter {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
emitCodeBlock() {
|
|
68
|
-
if (this.codeBuffer) {
|
|
68
|
+
if (this.codeBuffer.length > 0) {
|
|
69
69
|
const codeParts = this.parseCodeBuffer();
|
|
70
70
|
if (codeParts) {
|
|
71
71
|
const { language, currentStart, newEnd, currentCode, newCode } = codeParts;
|
|
@@ -80,6 +80,10 @@ class BlockManager extends events_1.EventEmitter {
|
|
|
80
80
|
this.emit('content', this.buffer);
|
|
81
81
|
this.clearBuffer();
|
|
82
82
|
}
|
|
83
|
+
else {
|
|
84
|
+
this.emit('content', this.codeBuffer);
|
|
85
|
+
this.clearBuffer();
|
|
86
|
+
}
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
@@ -48,6 +48,8 @@ class ChatManager {
|
|
|
48
48
|
this.prompt = '';
|
|
49
49
|
this.isProcessing = false;
|
|
50
50
|
this.isSilent = false;
|
|
51
|
+
this.retryCount = 0;
|
|
52
|
+
this.maxRetries = 3;
|
|
51
53
|
this.isSilent = options.silent || false;
|
|
52
54
|
options.stream = true;
|
|
53
55
|
options.silent = true;
|
|
@@ -597,22 +599,34 @@ class ChatManager {
|
|
|
597
599
|
* @param accumulatedContent - A string to accumulate the complete response content.
|
|
598
600
|
*/
|
|
599
601
|
handleResponseStream(responseStream, accumulatedContent) {
|
|
602
|
+
let animationInterval;
|
|
603
|
+
this.blockManager = new BlockManager_1.default();
|
|
604
|
+
this.chunkManager = new ChunkManager_1.default();
|
|
605
|
+
this.blockManager.on('startGeneratingCode', () => {
|
|
606
|
+
process.stdout.write(colors_1.default.gray.bold('\nGenerating code... '));
|
|
607
|
+
let animationIndex = 0;
|
|
608
|
+
const animationChars = ['/', '|', '\\', '-'];
|
|
609
|
+
animationInterval = setInterval(() => {
|
|
610
|
+
process.stdout.write('\b'.repeat(1));
|
|
611
|
+
process.stdout.write(colors_1.default.gray.bold(animationChars[animationIndex]));
|
|
612
|
+
animationIndex = (animationIndex + 1) % animationChars.length;
|
|
613
|
+
}, 100);
|
|
614
|
+
});
|
|
615
|
+
this.blockManager.on('endGeneratingCode', () => {
|
|
616
|
+
clearInterval(animationInterval);
|
|
617
|
+
process.stdout.write('\b'.repeat(100));
|
|
618
|
+
});
|
|
600
619
|
this.blockManager.on('content', (content) => {
|
|
601
|
-
process.stdout.write('\n------ beginning of blockManager content ------\n');
|
|
602
620
|
process.stdout.write(content);
|
|
603
|
-
process.stdout.write('\n------ end of blockManager content ------\n');
|
|
604
621
|
accumulatedContent += content;
|
|
605
622
|
});
|
|
606
623
|
this.blockManager.on('codeBlock', (codeBlock) => {
|
|
607
|
-
process.stdout.write(codeBlock.language);
|
|
608
|
-
process.stdout.write(
|
|
609
|
-
process.stdout.write(codeBlock.currentStart);
|
|
610
|
-
process.stdout.write('\n');
|
|
624
|
+
process.stdout.write(colors_1.default.gray.bold(`\`\`\`${codeBlock.language} \n`));
|
|
625
|
+
process.stdout.write(colors_1.default.gray.bold(`${codeBlock.currentStart}\n`));
|
|
611
626
|
process.stdout.write((0, cli_highlight_1.default)(codeBlock.currentCode, { language: codeBlock.language }));
|
|
612
|
-
process.stdout.write('\n=======\n');
|
|
627
|
+
process.stdout.write(colors_1.default.gray.bold('\n=======\n'));
|
|
613
628
|
process.stdout.write((0, cli_highlight_1.default)(codeBlock.newCode, { language: codeBlock.language }));
|
|
614
|
-
process.stdout.write('\n');
|
|
615
|
-
process.stdout.write(codeBlock.newEnd);
|
|
629
|
+
process.stdout.write(colors_1.default.gray.bold('\n>>>>>>> NEW\n```'));
|
|
616
630
|
accumulatedContent += codeBlock.raw;
|
|
617
631
|
});
|
|
618
632
|
// Handle content chunks from the ChunkManager
|
|
@@ -620,10 +634,6 @@ class ChatManager {
|
|
|
620
634
|
let convertedStream = this.gpt.convertStream(buffer);
|
|
621
635
|
while (convertedStream !== null) {
|
|
622
636
|
const { output, lastChar } = convertedStream;
|
|
623
|
-
process.stdout.write('\n------ beginning of chunk ------\n');
|
|
624
|
-
process.stdout.write(output);
|
|
625
|
-
process.stdout.write(`lastChar: ${lastChar}\n`);
|
|
626
|
-
process.stdout.write('\n------ end of chunk ------\n');
|
|
627
637
|
// buffer the output to the BlockManager
|
|
628
638
|
this.blockManager.addContent(output);
|
|
629
639
|
// clear the buffer from the ChunkManager
|
|
@@ -640,9 +650,6 @@ class ChatManager {
|
|
|
640
650
|
return;
|
|
641
651
|
// a chunk is a JSON object with different keys depending on the AI service provider
|
|
642
652
|
const chunkString = chunk.toString();
|
|
643
|
-
process.stdout.write('\n------ beginning of chunk string ------\n');
|
|
644
|
-
process.stdout.write(chunkString);
|
|
645
|
-
process.stdout.write('\n------ end of chunk string ------\n');
|
|
646
653
|
this.chunkManager.addChunk(chunkString);
|
|
647
654
|
});
|
|
648
655
|
// Handle the end of the response stream
|
|
@@ -658,69 +665,6 @@ class ChatManager {
|
|
|
658
665
|
this.rl.prompt();
|
|
659
666
|
});
|
|
660
667
|
}
|
|
661
|
-
processOutput(output, accumulatedContent, codeBuffer, isBufferingCode) {
|
|
662
|
-
const codeBlockMarker = '\n```';
|
|
663
|
-
let currentIndex = 0;
|
|
664
|
-
while (currentIndex < output.length) {
|
|
665
|
-
let markerIndex = output.indexOf(codeBlockMarker, currentIndex);
|
|
666
|
-
if (output.startsWith('```'))
|
|
667
|
-
markerIndex = 0;
|
|
668
|
-
// Check if the code block marker is found
|
|
669
|
-
if (markerIndex !== -1) {
|
|
670
|
-
if (isBufferingCode) {
|
|
671
|
-
// End of code block
|
|
672
|
-
codeBuffer += output.slice(currentIndex, markerIndex + codeBlockMarker.length);
|
|
673
|
-
const { language, currentStart, newEnd, current: currentCode, new: newCode } = this.parseCodeBuffer(codeBuffer);
|
|
674
|
-
if (language)
|
|
675
|
-
process.stdout.write(language);
|
|
676
|
-
if (currentStart)
|
|
677
|
-
process.stdout.write(currentStart);
|
|
678
|
-
if (currentCode)
|
|
679
|
-
process.stdout.write((0, cli_highlight_1.default)(currentCode, { language }));
|
|
680
|
-
process.stdout.write('=======');
|
|
681
|
-
if (newCode)
|
|
682
|
-
process.stdout.write((0, cli_highlight_1.default)(newCode, { language }));
|
|
683
|
-
if (newEnd)
|
|
684
|
-
process.stdout.write(newEnd);
|
|
685
|
-
accumulatedContent += codeBuffer;
|
|
686
|
-
codeBuffer = '';
|
|
687
|
-
isBufferingCode = false;
|
|
688
|
-
currentIndex = markerIndex + codeBlockMarker.length;
|
|
689
|
-
}
|
|
690
|
-
else {
|
|
691
|
-
// Start of code block
|
|
692
|
-
process.stdout.write(output.slice(currentIndex, markerIndex + codeBlockMarker.length));
|
|
693
|
-
accumulatedContent += output.slice(currentIndex, markerIndex + codeBlockMarker.length);
|
|
694
|
-
codeBuffer = '';
|
|
695
|
-
isBufferingCode = true;
|
|
696
|
-
currentIndex = markerIndex + codeBlockMarker.length;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
// No code block marker found
|
|
701
|
-
if (isBufferingCode) {
|
|
702
|
-
// buffering code
|
|
703
|
-
codeBuffer += output.slice(currentIndex);
|
|
704
|
-
}
|
|
705
|
-
else {
|
|
706
|
-
process.stdout.write(output.slice(currentIndex));
|
|
707
|
-
accumulatedContent += output.slice(currentIndex);
|
|
708
|
-
}
|
|
709
|
-
break;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
return [isBufferingCode, codeBuffer, accumulatedContent];
|
|
713
|
-
}
|
|
714
|
-
parseCodeBuffer(codeBuffer) {
|
|
715
|
-
const lines = codeBuffer.split('\n');
|
|
716
|
-
const language = lines[0].trim();
|
|
717
|
-
const currentStartIndex = lines.findIndex(line => line.startsWith('<<<<<<< CURRENT'));
|
|
718
|
-
const dividerIndex = lines.findIndex(line => line === '=======');
|
|
719
|
-
const newEndIndex = lines.findIndex(line => line.startsWith('>>>>>>> NEW'));
|
|
720
|
-
const current = lines.slice(currentStartIndex + 1, dividerIndex).join('\n');
|
|
721
|
-
const newCode = lines.slice(dividerIndex + 1, newEndIndex).join('\n');
|
|
722
|
-
return { language, currentStart: lines[currentStartIndex], newEnd: lines[newEndIndex], current, new: newCode };
|
|
723
|
-
}
|
|
724
668
|
async finalizeResponse(accumulatedContent) {
|
|
725
669
|
this.chatHistory.push({ role: 'assistant', content: accumulatedContent.trim() });
|
|
726
670
|
this.isProcessing = false;
|
|
@@ -728,12 +672,23 @@ class ChatManager {
|
|
|
728
672
|
const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(accumulatedContent);
|
|
729
673
|
const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
|
|
730
674
|
if (!canApply) {
|
|
731
|
-
|
|
732
|
-
|
|
675
|
+
if (this.retryCount < this.maxRetries) {
|
|
676
|
+
console.log(`\n\n${summary}\n\n${colors_1.default.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied. Retrying (Attempt ${this.retryCount + 1}/${this.maxRetries})...\n`);
|
|
677
|
+
this.retryCount++;
|
|
678
|
+
this.processUserInput(`Some of the provided code blocks could not be applied,
|
|
733
679
|
please match the existing code with a few more lines of context and make sure it is a character for character exact match.
|
|
680
|
+
Never use "rest of the existing code" or "rest of the code" in your response, instead break the code block into smaller blocks.
|
|
734
681
|
|
|
735
682
|
${summary}`);
|
|
736
|
-
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
console.log(`\n\n${colors_1.default.red.bold('ERROR')} Failed to generate a diff that could be cleanly applied after ${this.maxRetries} attempts. Please review the changes manually and try again.\n`);
|
|
687
|
+
this.retryCount = 0; // Reset the retry count for future attempts
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
this.retryCount = 0; // Reset the retry count on successful application
|
|
737
692
|
}
|
|
738
693
|
}
|
|
739
694
|
process.stdout.write(`
|
|
@@ -40,9 +40,14 @@ Please briefly explain how this shell script works.`;
|
|
|
40
40
|
console.log((0, cli_highlight_1.default)(generatedShellWithoutShebang));
|
|
41
41
|
}
|
|
42
42
|
async handleUserAction(response, prompt) {
|
|
43
|
+
let { exec } = this.options;
|
|
43
44
|
this.chatHistory.push({ role: 'assistant', content: response });
|
|
44
45
|
const generatedShell = (0, string_utils_1.extractMarkdown)(response);
|
|
45
46
|
const generatedShellWithoutShebang = generatedShell.replace(/^#!.*?\s/, '');
|
|
47
|
+
if (exec) {
|
|
48
|
+
(0, child_process_1.execSync)(generatedShellWithoutShebang, { stdio: 'inherit' });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
46
51
|
const action = await (0, prompts_1.select)({
|
|
47
52
|
message: 'What would you like to do?',
|
|
48
53
|
choices: [
|
package/dist/package.json
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.checkBlocksApplicability = exports.applyAndSaveCurrentNewBlocks = exports.
|
|
6
|
+
exports.checkBlocksApplicability = exports.applyAndSaveCurrentNewBlocks = exports.applyAndSaveBlock = exports.searchReplaceString = exports.writeFileContent = exports.ensureDirectoryExists = exports.extractCurrentNewBlocks = exports.extractBlock = exports.findBlockIndices = exports.parseMarkdownInstructions = exports.extractMarkdown = exports.extractJsonMetadata = exports.estimateTokens = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const colors_1 = __importDefault(require("colors"));
|
|
@@ -286,13 +286,13 @@ exports.writeFileContent = writeFileContent;
|
|
|
286
286
|
* currentContent: "console.log('Old content')",
|
|
287
287
|
* newContent: "console.log('New content')"
|
|
288
288
|
* }
|
|
289
|
-
* const updatedContent =
|
|
289
|
+
* const updatedContent = searchReplaceString(currentContent, block)
|
|
290
290
|
* // updatedContent will be:
|
|
291
291
|
* // function example() {
|
|
292
292
|
* // console.log('New content')
|
|
293
293
|
* // }
|
|
294
294
|
*/
|
|
295
|
-
const
|
|
295
|
+
const searchReplaceString = (currentContent, block) => {
|
|
296
296
|
// 1) if block.currentContent.trim() is empty then return block.newContent
|
|
297
297
|
if (block.currentContent.trim() === '') {
|
|
298
298
|
return block.newContent;
|
|
@@ -308,26 +308,32 @@ const updateFileContent = (currentContent, block) => {
|
|
|
308
308
|
if (matchingLine === -1) {
|
|
309
309
|
return currentContent; // If no match is found, return the original content
|
|
310
310
|
}
|
|
311
|
-
// 5) compare the first line of block.currentContent with the
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
311
|
+
// 5) compare the first line of block.currentContent with the startLine line of currentContent, putting any preceeding spaces that are in currentContent but not in block.currentContent into a variable named preceedingSpaces
|
|
312
|
+
const startLine = currentContent.split('\n').findIndex((line) => line.includes(blockLines[0]));
|
|
313
|
+
if (startLine === -1) {
|
|
314
|
+
return currentContent; // If no match is found, return the original content
|
|
315
|
+
}
|
|
316
|
+
const startLineContent = currentContent.split('\n')[startLine];
|
|
317
|
+
const blockLineContent = block.currentContent.split('\n')[0];
|
|
318
|
+
const precedingSpaces = startLineContent.replace(blockLineContent, '');
|
|
316
319
|
// 6) add preceedingSpaces to the start of each line in both block.currentContent and block.newContent
|
|
317
320
|
if (precedingSpaces) {
|
|
318
|
-
const
|
|
321
|
+
const indentedCurrentContent = block.currentContent
|
|
322
|
+
.split('\n')
|
|
323
|
+
.map((line) => precedingSpaces + line)
|
|
324
|
+
.join('\n');
|
|
319
325
|
const indentedNewContent = block.newContent
|
|
320
326
|
.split('\n')
|
|
321
|
-
.map((line) =>
|
|
327
|
+
.map((line) => precedingSpaces + line)
|
|
322
328
|
.join('\n');
|
|
323
329
|
// 7) finally, do a simple currentContent.replace(block.currentContent, block.newContent) with the modified (added preceeding spaces to match) variables
|
|
324
|
-
return currentContent.replace(
|
|
330
|
+
return currentContent.replace(indentedCurrentContent, indentedNewContent);
|
|
325
331
|
}
|
|
326
332
|
else {
|
|
327
333
|
return currentContent.replace(block.currentContent, block.newContent);
|
|
328
334
|
}
|
|
329
335
|
};
|
|
330
|
-
exports.
|
|
336
|
+
exports.searchReplaceString = searchReplaceString;
|
|
331
337
|
/**
|
|
332
338
|
* Analyzes the content of a block against the file content to find matching lines.
|
|
333
339
|
*
|
|
@@ -364,7 +370,7 @@ const analyzeBlockContent = async (block, fileContent) => {
|
|
|
364
370
|
* @param {number} index - The index of the block being processed (used for logging).
|
|
365
371
|
* @returns {Promise<void>} A promise that resolves when the block has been processed.
|
|
366
372
|
*/
|
|
367
|
-
const
|
|
373
|
+
const applyAndSaveBlock = async (block, index) => {
|
|
368
374
|
const filePath = path_1.default.resolve(block.filePath);
|
|
369
375
|
let fileContent = await readFileContent(filePath);
|
|
370
376
|
if (fileContent === '') {
|
|
@@ -381,11 +387,11 @@ const processBlock = async (block, index) => {
|
|
|
381
387
|
console.log(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold(`ERROR: Current content matches multiple (${matchingLines.length}) parts in the file`)}`);
|
|
382
388
|
return;
|
|
383
389
|
}
|
|
384
|
-
const updatedContent = (0, exports.
|
|
390
|
+
const updatedContent = (0, exports.searchReplaceString)(fileContent, block);
|
|
385
391
|
await (0, exports.writeFileContent)(filePath, updatedContent);
|
|
386
392
|
console.log(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} has been ${colors_1.default.yellow.bold('updated')}`);
|
|
387
393
|
};
|
|
388
|
-
exports.
|
|
394
|
+
exports.applyAndSaveBlock = applyAndSaveBlock;
|
|
389
395
|
/**
|
|
390
396
|
* Applies and saves the changes specified in the CurrentNewBlock array.
|
|
391
397
|
*
|
|
@@ -413,7 +419,7 @@ const applyAndSaveCurrentNewBlocks = async (blocks) => {
|
|
|
413
419
|
console.log(`\nApplying and saving current/new blocks...\n`);
|
|
414
420
|
for (const [index, block] of blocks.entries()) {
|
|
415
421
|
try {
|
|
416
|
-
await (0, exports.
|
|
422
|
+
await (0, exports.applyAndSaveBlock)(block, index);
|
|
417
423
|
}
|
|
418
424
|
catch (error) {
|
|
419
425
|
console.error(`Error processing ${colors_1.default.red(block.filePath)}:`, error);
|