cloving 0.3.4 → 0.3.5

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.
@@ -18,7 +18,7 @@ const init = async (options) => {
18
18
  options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
19
19
  const gpt = new cloving_gpt_1.default(options);
20
20
  const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
21
- const specialFileNames = Object.keys(specialFileContents).map(file => ' - ' + file);
21
+ const specialFileNames = Object.keys(specialFileContents).map((file) => ' - ' + file);
22
22
  // Initialize chat history
23
23
  const chatHistory = [
24
24
  {
@@ -146,14 +146,14 @@ Here is an example response:
146
146
  }
147
147
  ],
148
148
  "projectType": "Command-line tool",
149
- }`
150
- }
149
+ }`,
150
+ },
151
151
  ];
152
152
  if (!options.silent) {
153
153
  if (specialFileNames.length > 0) {
154
154
  console.log(`Cloving will analyze the list of files and the contents of the following files:
155
155
 
156
- ${specialFileNames.join("\n")}
156
+ ${specialFileNames.join('\n')}
157
157
 
158
158
  Cloving will send AI a request to summarize the technologies used in this project.
159
159
 
@@ -184,7 +184,7 @@ technologies used. This will provide better context for future Cloving requests.
184
184
  ig.add(gitignoreContent);
185
185
  }
186
186
  const fileList = await (0, command_utils_1.generateFileList)();
187
- const filteredFileList = fileList.filter(file => {
187
+ const filteredFileList = fileList.filter((file) => {
188
188
  try {
189
189
  return !ig.ignores(file);
190
190
  }
@@ -195,7 +195,7 @@ technologies used. This will provide better context for future Cloving requests.
195
195
  const limitedFileList = filteredFileList.slice(0, 100);
196
196
  const projectDetails = {
197
197
  files: limitedFileList,
198
- specialFiles: specialFileContents
198
+ specialFiles: specialFileContents,
199
199
  };
200
200
  const prompt = `Here is a JSON object describing my project:
201
201
  ${JSON.stringify(projectDetails, null, 2)}`;
@@ -14,9 +14,12 @@ const tokens = async (options) => {
14
14
  let expandedFiles = [];
15
15
  for (const file of files) {
16
16
  const filePath = path_1.default.resolve(file);
17
- if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
17
+ if (await fs_1.default.promises
18
+ .stat(filePath)
19
+ .then((stat) => stat.isDirectory())
20
+ .catch(() => false)) {
18
21
  const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
19
- expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
22
+ expandedFiles = expandedFiles.concat(dirFiles.map((f) => path_1.default.relative(process.cwd(), f)));
20
23
  }
21
24
  else {
22
25
  expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
@@ -25,12 +28,17 @@ const tokens = async (options) => {
25
28
  files = expandedFiles;
26
29
  for (const file of files) {
27
30
  const filePath = path_1.default.resolve(file);
28
- if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
31
+ if (await fs_1.default.promises
32
+ .stat(filePath)
33
+ .then((stat) => stat.isFile())
34
+ .catch(() => false)) {
29
35
  const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
30
36
  contextFiles[file] = content;
31
37
  }
32
38
  }
33
- const contextFileContents = Object.keys(contextFiles).map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`).join('\n');
39
+ const contextFileContents = Object.keys(contextFiles)
40
+ .map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`)
41
+ .join('\n');
34
42
  prompt += `
35
43
  ### Description of App
36
44
 
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ program
36
36
  program
37
37
  .command('chat')
38
38
  .option('-f, --files <filenames...>', 'Specify filenames of files with context to use for generating code')
39
+ .option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
39
40
  .option('-t, --temperature <temperature>', 'Temperature for the model (default 0.2)')
40
41
  .option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5:sonnet-20240620)')
41
42
  .description('Start an interactive chat with cloving')
@@ -47,11 +48,7 @@ program
47
48
  .option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5:sonnet-20240620)')
48
49
  .description('Start a proxy server to use with cloving')
49
50
  .action(proxy_1.default);
50
- program
51
- .command('models')
52
- .alias('m')
53
- .description('List available models')
54
- .action(models_1.default);
51
+ program.command('models').alias('m').description('List available models').action(models_1.default);
55
52
  program
56
53
  .command('tokens')
57
54
  .alias('t')
@@ -106,7 +103,7 @@ generate
106
103
  generate
107
104
  .command('unit-tests')
108
105
  .alias('u')
109
- .description('Generate unit tests (if you don\'t specify filenames, it will generate tests for commited changes that differ from the main/master branch)')
106
+ .description("Generate unit tests (if you don't specify filenames, it will generate tests for commited changes that differ from the main/master branch)")
110
107
  .option('--save', 'Automatically save all files after generating the unit tests')
111
108
  .option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
112
109
  .option('-f, --files <filenames...>', 'Specify filenames for the unit tests')
@@ -27,7 +27,7 @@ const specialCommands = [
27
27
  'ls <pattern>',
28
28
  'git <command>',
29
29
  'help',
30
- 'exit'
30
+ 'exit',
31
31
  ];
32
32
  class ChatManager {
33
33
  /**
@@ -87,11 +87,11 @@ class ChatManager {
87
87
  console.log(` - ${colors_1.default.yellow(colors_1.default.bold('exit'))} Quit this session`);
88
88
  }
89
89
  /**
90
- * Checks for the latest version of the app from GitHub and compares it with the current version.
91
- * If a new version is available, it notifies the user.
92
- * @private
93
- * @returns {Promise<void>}
94
- */
90
+ * Checks for the latest version of the app from GitHub and compares it with the current version.
91
+ * If a new version is available, it notifies the user.
92
+ * @private
93
+ * @returns {Promise<void>}
94
+ */
95
95
  async checkForLatestVersion() {
96
96
  try {
97
97
  const response = await axios_1.default.get('https://api.github.com/repos/cloving-ai/cloving-cli/releases/latest');
@@ -123,9 +123,9 @@ class ChatManager {
123
123
  */
124
124
  async loadContextFiles() {
125
125
  const config = (0, config_utils_1.getClovingConfig)();
126
- const primaryLanguage = config.languages.find(lang => lang.primary);
126
+ const primaryLanguage = config.languages.find((lang) => lang.primary);
127
127
  const defaultDirectory = primaryLanguage ? primaryLanguage.directory : '.';
128
- const testingDirectories = config.testingFrameworks?.map(framework => framework.directory) || [];
128
+ const testingDirectories = config.testingFrameworks?.map((framework) => framework.directory) || [];
129
129
  const testingDirectory = testingDirectories[0];
130
130
  let files = this.options.files || [defaultDirectory, testingDirectory].filter(Boolean);
131
131
  if (files.length > 0) {
@@ -235,6 +235,13 @@ class ChatManager {
235
235
  * - Any other input is processed as a user query to the AI.
236
236
  */
237
237
  async handleCommand(command) {
238
+ // Check if the command is a single character and map it to the first matching special command
239
+ if (command.length === 1) {
240
+ const matchingCommand = specialCommands.find((cmd) => cmd.startsWith(command));
241
+ if (matchingCommand) {
242
+ command = matchingCommand.split(' ')[0]; // Use only the command part without arguments
243
+ }
244
+ }
238
245
  switch (command) {
239
246
  case 'help':
240
247
  this.displayHelp();
@@ -249,10 +256,10 @@ class ChatManager {
249
256
  await this.handleCommit();
250
257
  break;
251
258
  case 'ls':
252
- this.handleList("ls *");
259
+ this.handleList('ls *');
253
260
  break;
254
261
  case 'rm':
255
- await this.handleRemove("rm *");
262
+ await this.handleRemove('rm *');
256
263
  break;
257
264
  case 'review':
258
265
  await this.handleReview();
@@ -317,8 +324,10 @@ class ChatManager {
317
324
  if (error) {
318
325
  return reject(error);
319
326
  }
320
- const files = stdout.split('\n').filter((filePath) => filePath.trim() !== '')
321
- .map((filePath) => filePath.startsWith('./') ? filePath.slice(2) : filePath);
327
+ const files = stdout
328
+ .split('\n')
329
+ .filter((filePath) => filePath.trim() !== '')
330
+ .map((filePath) => (filePath.startsWith('./') ? filePath.slice(2) : filePath));
322
331
  resolve(files);
323
332
  });
324
333
  });
@@ -367,14 +376,14 @@ class ChatManager {
367
376
  // Function to filter paths using the pattern
368
377
  filterPaths(paths, pattern) {
369
378
  const regex = this.globToRegExp(pattern);
370
- return paths.filter(path => regex.test(path));
379
+ return paths.filter((path) => regex.test(path));
371
380
  }
372
381
  async handleRemove(command) {
373
382
  const pattern = command.slice(3).trim();
374
383
  if (pattern) {
375
384
  const matchedFiles = this.filterPaths(Object.keys(this.contextFiles), pattern);
376
385
  if (matchedFiles.length > 0) {
377
- matchedFiles.forEach(filePath => {
386
+ matchedFiles.forEach((filePath) => {
378
387
  delete this.contextFiles[filePath];
379
388
  console.log(`Removed ${colors_1.default.bold(colors_1.default.green(filePath))} from this chat session's context files`);
380
389
  });
@@ -402,7 +411,7 @@ class ChatManager {
402
411
  if (matchedFiles.length > 0) {
403
412
  console.log(`\nFiles in the current chat session context:`);
404
413
  let totalTokens = 0;
405
- matchedFiles.forEach(fileName => {
414
+ matchedFiles.forEach((fileName) => {
406
415
  const content = this.contextFiles[fileName];
407
416
  const tokenEstimate = this.estimateTokens(content);
408
417
  totalTokens += tokenEstimate;
@@ -417,7 +426,7 @@ class ChatManager {
417
426
  this.rl.prompt();
418
427
  }
419
428
  async handleCopy() {
420
- const lastResponse = this.chatHistory.filter(msg => msg.role === 'assistant').pop();
429
+ const lastResponse = this.chatHistory.filter((msg) => msg.role === 'assistant').pop();
421
430
  if (lastResponse) {
422
431
  copy_paste_1.default.copy(lastResponse.content, () => {
423
432
  console.info('Last response copied to clipboard.');
@@ -443,7 +452,10 @@ class ChatManager {
443
452
  this.rl.prompt();
444
453
  }
445
454
  async handleSave() {
446
- const lastResponse = this.chatHistory.slice().reverse().find(msg => msg.role === 'assistant');
455
+ const lastResponse = this.chatHistory
456
+ .slice()
457
+ .reverse()
458
+ .find((msg) => msg.role === 'assistant');
447
459
  if (lastResponse) {
448
460
  const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(lastResponse.content);
449
461
  if (Object.keys(currentNewBlocks).length > 0) {
@@ -497,8 +509,11 @@ class ChatManager {
497
509
  this.prompt = this.generatePrompt(input);
498
510
  this.addChatHistory(this.prompt);
499
511
  if (!this.isSilent) {
500
- const fullPrompt = this.chatHistory.map(m => {
501
- const content = m.content.split('\n').map(line => {
512
+ const fullPrompt = this.chatHistory
513
+ .map((m) => {
514
+ const content = m.content
515
+ .split('\n')
516
+ .map((line) => {
502
517
  if (line.startsWith('###')) {
503
518
  return colors_1.default.red.bold(line);
504
519
  }
@@ -506,21 +521,27 @@ class ChatManager {
506
521
  return colors_1.default.yellow.bold(line);
507
522
  }
508
523
  return line;
509
- }).join('\n');
510
- return m.role === 'user' ? `${colors_1.default.yellow.bold('## User Prompt')}\n\n${content}` : `${colors_1.default.green.bold('## 🍀 Cloving\'s Response 🍀')}\n\n${content}`;
511
- }).join('\n\n');
524
+ })
525
+ .join('\n');
526
+ return m.role === 'user'
527
+ ? `${colors_1.default.yellow.bold('## User Prompt')}\n\n${content}`
528
+ : `${colors_1.default.green.bold("## 🍀 Cloving's Response 🍀")}\n\n${content}`;
529
+ })
530
+ .join('\n\n');
512
531
  // Review the prompt using the REPL
513
532
  const tokenCount = Math.ceil(fullPrompt.length / 4).toLocaleString();
514
- console.log(`The generated prompt is approximately ${tokenCount} tokens long. Would you like to review the prompt before sending it? ${colors_1.default.gray('(Y/n)')}`);
533
+ console.log(`${colors_1.default.yellow.bold('INFORMATION')} The generated prompt is approximately ${tokenCount} tokens long. Would you like to review the prompt before sending it? ${colors_1.default.gray('(Y/n)')}`);
515
534
  const reviewPrompt = await new Promise((resolve) => {
516
535
  this.rl.question(colors_1.default.green.bold('cloving> '), (answer) => {
517
536
  resolve(answer.trim().toLowerCase());
518
537
  });
519
538
  });
520
- if (reviewPrompt === 'y' || reviewPrompt === '') {
539
+ if (reviewPrompt.startsWith('y') || reviewPrompt === '') {
521
540
  console.log(colors_1.default.gray.bold('\n---------------------- PROMPT START ----------------------\n'));
522
541
  const promptParts = fullPrompt.split('\n```');
523
- const highlightedPrompt = promptParts.map((part, index) => index % 2 === 1 ? (0, cli_highlight_1.default)(part, { language: 'typescript' }) : part).join('\n```');
542
+ const highlightedPrompt = promptParts
543
+ .map((part, index) => index % 2 === 1 ? (0, cli_highlight_1.default)(part, { language: 'typescript' }) : part)
544
+ .join('\n```');
524
545
  console.log(highlightedPrompt);
525
546
  console.log(colors_1.default.gray.bold('\n---------------------- PROMPT END ----------------------\n'));
526
547
  console.log(`Do you want to proceed with this prompt? ${colors_1.default.gray('(Y/n)')}`);
@@ -529,7 +550,7 @@ class ChatManager {
529
550
  resolve(answer.trim().toLowerCase());
530
551
  });
531
552
  });
532
- if (confirmPrompt !== 'y' && confirmPrompt !== '') {
553
+ if (!confirmPrompt.startsWith('y') && confirmPrompt !== '') {
533
554
  console.log('Operation cancelled by user.');
534
555
  this.isProcessing = false;
535
556
  this.rl.prompt();
@@ -537,7 +558,10 @@ class ChatManager {
537
558
  }
538
559
  }
539
560
  }
540
- const responseStream = await this.gpt.streamText({ prompt: this.prompt, messages: this.chatHistory });
561
+ const responseStream = await this.gpt.streamText({
562
+ prompt: this.prompt,
563
+ messages: this.chatHistory,
564
+ });
541
565
  let accumulatedContent = '';
542
566
  this.handleResponseStream(responseStream, accumulatedContent);
543
567
  }
@@ -574,9 +598,28 @@ class ChatManager {
574
598
  this.rl.prompt();
575
599
  });
576
600
  }
577
- finalizeResponse(accumulatedContent) {
601
+ async finalizeResponse(accumulatedContent) {
578
602
  this.chatHistory.push({ role: 'assistant', content: accumulatedContent.trim() });
579
603
  this.isProcessing = false;
604
+ if (accumulatedContent) {
605
+ const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(accumulatedContent);
606
+ const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
607
+ if (!canApply) {
608
+ console.log(`\n\n${summary}\n\n${colors_1.default.red.bold('WARNING')} Some of the provided code blocks could not be automatically applied, prompt the AI to try again with more detail? ${colors_1.default.gray('(Y/n)')}`);
609
+ const reviewPrompt = await new Promise((resolve) => {
610
+ this.rl.question(colors_1.default.green.bold('cloving> '), (answer) => {
611
+ resolve(answer.trim().toLowerCase());
612
+ });
613
+ });
614
+ if (reviewPrompt.startsWith('y') || reviewPrompt === '') {
615
+ this.processUserInput(`Some of the provided code blocks could not be applied,
616
+ please match the existing code with a few more lines of context and make sure it is a character for character exact match.
617
+
618
+ ${summary}`);
619
+ return;
620
+ }
621
+ }
622
+ }
580
623
  process.stdout.write(`
581
624
 
582
625
  You can follow up with another request or:
@@ -598,20 +641,21 @@ You can follow up with another request or:
598
641
  const errorNumber = error.response?.status || 'unknown';
599
642
  switch (errorNumber) {
600
643
  case 400:
601
- errorMessage = "Invalid model or prompt size too large. Try specifying fewer files.";
644
+ errorMessage = 'Invalid model or prompt size too large. Try specifying fewer files.';
602
645
  break;
603
646
  case 403:
604
- errorMessage = "Inactive subscription or usage limit reached";
647
+ errorMessage = 'Inactive subscription or usage limit reached';
605
648
  break;
606
649
  case 429:
607
- errorMessage = "Rate limit error";
650
+ errorMessage = 'Rate limit error';
608
651
  break;
609
652
  case 500:
610
- errorMessage = "Internal server error";
653
+ errorMessage = 'Internal server error';
611
654
  break;
612
655
  }
613
656
  const promptTokens = Math.ceil(this.prompt.length / 4).toLocaleString();
614
- console.error(`Error processing a ${promptTokens} token prompt: `, errorMessage, `(${errorNumber}) \n`);
657
+ const statusMessage = error.response?.data?.statusMessage;
658
+ console.error(colors_1.default.red(`\nError (${errorNumber}) processing a ${promptTokens} token prompt\n\n${statusMessage}\n`));
615
659
  this.isProcessing = false;
616
660
  this.rl.prompt();
617
661
  }
@@ -43,6 +43,17 @@ class CodeManager {
43
43
  this.options.prompt = userPrompt;
44
44
  }
45
45
  let response = await this.generateCode(this.options.prompt);
46
+ const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(response);
47
+ const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
48
+ if (!canApply) {
49
+ console.log('The generated code could not be automatically applied. It will try again asking for more details.');
50
+ console.log(`Some of the provided code blocks could not be applied,
51
+ please match the existing code with a few more lines of context and make sure it is a character for character exact match.
52
+
53
+ ${summary}`);
54
+ response = await this.generateCode(this.options.prompt);
55
+ this.displayGeneratedCode(response);
56
+ }
46
57
  this.displayGeneratedCode(response);
47
58
  if (this.options.save) {
48
59
  const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(response);
@@ -67,7 +78,7 @@ class CodeManager {
67
78
  return await this.gpt.generateText({ prompt: userPrompt, messages: this.chatHistory });
68
79
  }
69
80
  displayGeneratedCode(rawCodeCommand) {
70
- (0, string_utils_1.parseMarkdownInstructions)(rawCodeCommand).map(code => {
81
+ (0, string_utils_1.parseMarkdownInstructions)(rawCodeCommand).map((code) => {
71
82
  if (code.trim().startsWith('```')) {
72
83
  const lines = code.split('\n');
73
84
  const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
@@ -88,7 +99,7 @@ class CodeManager {
88
99
  }
89
100
  async handleUserAction(response, prompt) {
90
101
  const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(response);
91
- const files = Array.from(new Set(currentNewBlocks.map(block => block.filePath)));
102
+ const files = Array.from(new Set(currentNewBlocks.map((block) => block.filePath)));
92
103
  if (this.options.save) {
93
104
  await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(currentNewBlocks);
94
105
  return;
@@ -129,7 +140,10 @@ class CodeManager {
129
140
  case 'explain':
130
141
  const explainPrompt = this.generateExplainCodePrompt(response);
131
142
  this.chatHistory.push({ role: 'user', content: explainPrompt });
132
- const explainCodeCommand = await this.gpt.generateText({ prompt: explainPrompt, messages: this.chatHistory });
143
+ const explainCodeCommand = await this.gpt.generateText({
144
+ prompt: explainPrompt,
145
+ messages: this.chatHistory,
146
+ });
133
147
  console.log((0, cli_highlight_1.default)(explainCodeCommand, { language: 'markdown' }));
134
148
  break;
135
149
  case 'save':
@@ -137,9 +151,9 @@ class CodeManager {
137
151
  while (saveAnother) {
138
152
  const fileToSave = await (0, prompts_1.select)({
139
153
  message: 'Which file do you want to save?',
140
- choices: files.map(file => ({ name: file, value: file })),
154
+ choices: files.map((file) => ({ name: file, value: file })),
141
155
  });
142
- await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(currentNewBlocks.filter(block => block.filePath === fileToSave));
156
+ await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(currentNewBlocks.filter((block) => block.filePath === fileToSave));
143
157
  const saveMore = await (0, prompts_1.confirm)({
144
158
  message: 'Do you want to save another file?',
145
159
  default: true,
@@ -35,7 +35,9 @@ class CommitManager {
35
35
  fs_1.default.writeFileSync(tempCommitFilePath, commitMessage);
36
36
  // Commit the changes using the generated commit message
37
37
  try {
38
- (0, child_process_1.execFileSync)('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], { stdio: 'inherit' });
38
+ (0, child_process_1.execFileSync)('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], {
39
+ stdio: 'inherit',
40
+ });
39
41
  }
40
42
  catch (commitError) {
41
43
  // If commit is canceled (non-zero exit), handle it here
@@ -47,8 +49,9 @@ class CommitManager {
47
49
  throw err;
48
50
  });
49
51
  }
50
- catch (error) {
51
- console.error('Could not generate commit message:', error);
52
+ catch (err) {
53
+ const error = err;
54
+ console.error('Could not generate commit message:', error.message);
52
55
  }
53
56
  }
54
57
  }
@@ -38,9 +38,12 @@ class ProxyManager {
38
38
  let expandedFiles = [];
39
39
  for (const file of files) {
40
40
  const filePath = path_1.default.resolve(file);
41
- if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
41
+ if (await fs_1.default.promises
42
+ .stat(filePath)
43
+ .then((stat) => stat.isDirectory())
44
+ .catch(() => false)) {
42
45
  const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
43
- expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
46
+ expandedFiles = expandedFiles.concat(dirFiles.map((f) => path_1.default.relative(process.cwd(), f)));
44
47
  }
45
48
  else {
46
49
  expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
@@ -49,7 +52,10 @@ class ProxyManager {
49
52
  files = expandedFiles;
50
53
  for (const file of files) {
51
54
  const filePath = path_1.default.resolve(file);
52
- if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
55
+ if (await fs_1.default.promises
56
+ .stat(filePath)
57
+ .then((stat) => stat.isFile())
58
+ .catch(() => false)) {
53
59
  const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
54
60
  this.contextFiles[file] = content;
55
61
  }
@@ -59,7 +65,9 @@ class ProxyManager {
59
65
  try {
60
66
  const { messages, stream } = req.body;
61
67
  const content = messages.map((message) => message.content).join('\n\n');
62
- const contextFileContents = Object.keys(this.contextFiles).map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`).join('\n');
68
+ const contextFileContents = Object.keys(this.contextFiles)
69
+ .map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`)
70
+ .join('\n');
63
71
  const prompt = `# Task
64
72
 
65
73
  ${content}
@@ -99,10 +107,17 @@ ${content}`;
99
107
  let convertedStream = this.gpt.convertStream(buffer);
100
108
  while (convertedStream !== null) {
101
109
  const { output, lastChar } = convertedStream;
102
- const content = JSON.stringify({ id: "chatcmpl-9tLvSuRQaYzQIiKRAwI5a7WqdJspq", object: "chat.completion.chunk", created: 1722979350, model: "cloving", system_fingerprint: "fp_3aa7262c27", choices: [{ index: 0, delta: { content: output }, logprobs: null, finish_reason: null }] });
110
+ const content = JSON.stringify({
111
+ id: 'chatcmpl-9tLvSuRQaYzQIiKRAwI5a7WqdJspq',
112
+ object: 'chat.completion.chunk',
113
+ created: 1722979350,
114
+ model: 'cloving',
115
+ system_fingerprint: 'fp_3aa7262c27',
116
+ choices: [{ index: 0, delta: { content: output }, logprobs: null, finish_reason: null }],
117
+ });
103
118
  convertedStreams.push(content);
104
119
  if (convertedStreams.length >= 2) {
105
- const dataToSend = convertedStreams.map(c => `data: ${c}`).join('\n\n');
120
+ const dataToSend = convertedStreams.map((c) => `data: ${c}`).join('\n\n');
106
121
  res.write(`${dataToSend}\n\n`);
107
122
  convertedStreams = [];
108
123
  }
@@ -117,7 +132,7 @@ ${content}`;
117
132
  });
118
133
  responseStream.data.on('end', () => {
119
134
  // Send any remaining converted streams
120
- const dataToSend = convertedStreams.map(c => `data: ${c}`).join('\n\n');
135
+ const dataToSend = convertedStreams.map((c) => `data: ${c}`).join('\n\n');
121
136
  res.write(`${dataToSend}\n\ndata: [DONE]\n\n`);
122
137
  res.end();
123
138
  });
@@ -26,9 +26,12 @@ class ReviewManager {
26
26
  let expandedFiles = [];
27
27
  for (const file of this.options.files) {
28
28
  const filePath = path_1.default.resolve(file);
29
- if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
29
+ if (await fs_1.default.promises
30
+ .stat(filePath)
31
+ .then((stat) => stat.isDirectory())
32
+ .catch(() => false)) {
30
33
  const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
31
- expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
34
+ expandedFiles = expandedFiles.concat(dirFiles.map((f) => path_1.default.relative(process.cwd(), f)));
32
35
  }
33
36
  else {
34
37
  expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
@@ -37,7 +40,10 @@ class ReviewManager {
37
40
  this.options.files = expandedFiles;
38
41
  for (const file of this.options.files) {
39
42
  const filePath = path_1.default.resolve(file);
40
- if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
43
+ if (await fs_1.default.promises
44
+ .stat(filePath)
45
+ .then((stat) => stat.isFile())
46
+ .catch(() => false)) {
41
47
  const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
42
48
  this.contextFiles[file] = content;
43
49
  }
@@ -98,7 +104,9 @@ class ReviewManager {
98
104
 
99
105
  `;
100
106
  if (this.options.files) {
101
- const contextFileContents = Object.keys(this.contextFiles).map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`).join('\n');
107
+ const contextFileContents = Object.keys(this.contextFiles)
108
+ .map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`)
109
+ .join('\n');
102
110
  systemPrompt += `### Description of App
103
111
 
104
112
  ${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
@@ -122,7 +130,10 @@ Format the output of this code review in Markdown format.`;
122
130
  this.chatHistory.push({ role: 'user', content: systemPrompt });
123
131
  this.chatHistory.push({ role: 'assistant', content: 'What would you like to do?' });
124
132
  }
125
- this.chatHistory.push({ role: 'user', content: this.options.prompt || 'Please provide a code review.' });
133
+ this.chatHistory.push({
134
+ role: 'user',
135
+ content: this.options.prompt || 'Please provide a code review.',
136
+ });
126
137
  return this.options.prompt || 'Please provide a code review.';
127
138
  }
128
139
  async getGitDiffPrompt() {
@@ -137,7 +148,7 @@ ${this.options.prompt || 'Please provide a code review.'}`;
137
148
  return prompt;
138
149
  }
139
150
  displayAnalysis(analysis) {
140
- (0, string_utils_1.parseMarkdownInstructions)(analysis).map(code => {
151
+ (0, string_utils_1.parseMarkdownInstructions)(analysis).map((code) => {
141
152
  if (code.trim().startsWith('```')) {
142
153
  const lines = code.split('\n');
143
154
  const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
@@ -166,10 +177,13 @@ ${this.options.prompt || 'Please provide a code review.'}`;
166
177
  message: 'What would you like to copy to the clipboard?',
167
178
  choices: [
168
179
  { name: 'Copy only the Changes Overview', value: 'Changes Overview' },
169
- { name: 'Copy only the Potential Bugs and Recommended Fixes', value: 'Potential Bugs and Recommended Fixes' },
180
+ {
181
+ name: 'Copy only the Potential Bugs and Recommended Fixes',
182
+ value: 'Potential Bugs and Recommended Fixes',
183
+ },
170
184
  { name: 'Copy everything', value: 'Everything' },
171
- { name: 'Done', value: 'Done' }
172
- ]
185
+ { name: 'Done', value: 'Done' },
186
+ ],
173
187
  });
174
188
  let contentToCopy = '';
175
189
  switch (clipboardOption) {
@@ -200,7 +214,9 @@ ${this.options.prompt || 'Please provide a code review.'}`;
200
214
  async review() {
201
215
  try {
202
216
  await this.loadContextFiles();
203
- const prompt = this.options.files ? this.generateReviewPrompt() : await this.getGitDiffPrompt();
217
+ const prompt = this.options.files
218
+ ? this.generateReviewPrompt()
219
+ : await this.getGitDiffPrompt();
204
220
  const analysis = await this.gpt.generateText({ prompt, messages: this.chatHistory });
205
221
  this.displayAnalysis(analysis);
206
222
  await this.handleUserAction(analysis);
@@ -68,7 +68,10 @@ Please briefly explain how this shell script works.`;
68
68
  case 'explain':
69
69
  const explainPrompt = this.generateExplainShellPrompt(generatedShellWithoutShebang);
70
70
  this.chatHistory.push({ role: 'user', content: explainPrompt });
71
- const explainShellCommand = await this.gpt.generateText({ prompt: explainPrompt, messages: this.chatHistory });
71
+ const explainShellCommand = await this.gpt.generateText({
72
+ prompt: explainPrompt,
73
+ messages: this.chatHistory,
74
+ });
72
75
  console.log((0, cli_highlight_1.default)(explainShellCommand, { language: 'markdown' }));
73
76
  break;
74
77
  case 'copy':
@@ -91,7 +94,7 @@ Please briefly explain how this shell script works.`;
91
94
  try {
92
95
  if (!userPrompt) {
93
96
  userPrompt = await (0, prompts_1.input)({
94
- message: 'What would you like to do: '
97
+ message: 'What would you like to do: ',
95
98
  });
96
99
  }
97
100
  const rawShellCommand = await this.generateShell(userPrompt);