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('\n');
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
- console.log(`\n\n${summary}\n\n${colors_1.default.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied, prompting the AI to try again with more detail.\n`);
732
- this.processUserInput(`Some of the provided code blocks could not be applied,
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
- return;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "packageManager": "yarn@1.22.22",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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.processBlock = exports.updateFileContent = exports.writeFileContent = exports.ensureDirectoryExists = exports.extractCurrentNewBlocks = exports.extractBlock = exports.findBlockIndices = exports.parseMarkdownInstructions = exports.extractMarkdown = exports.extractJsonMetadata = exports.estimateTokens = void 0;
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 = updateFileContent(currentContent, block)
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 updateFileContent = (currentContent, block) => {
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 matchingLine line of currentContent, putting any preceeding spaces that are in currentContent but not in block.currentContent into a variable named preceedingSpaces
312
- const firstLineIndex = currentContent.indexOf(blockLines[0]);
313
- const precedingSpaces = currentContent
314
- .substring(firstLineIndex, firstLineIndex + currentLines[0].length)
315
- .match(/^\s*/);
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 precedingSpacesValue = precedingSpaces[0];
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) => precedingSpacesValue + 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(block.currentContent, indentedNewContent);
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.updateFileContent = updateFileContent;
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 processBlock = async (block, index) => {
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.updateFileContent)(fileContent, block);
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.processBlock = processBlock;
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.processBlock)(block, index);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "packageManager": "yarn@1.22.22",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",