cloving 0.3.3 → 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.
- package/dist/cloving_gpt/adapters/claude.js +7 -5
- package/dist/cloving_gpt/adapters/gemini.js +14 -9
- package/dist/cloving_gpt/adapters/mistral.js +9 -7
- package/dist/cloving_gpt/adapters/ollama.js +7 -7
- package/dist/cloving_gpt/adapters/openai.js +9 -7
- package/dist/cloving_gpt/index.js +12 -7
- package/dist/commands/config.js +7 -7
- package/dist/commands/generate/context.js +10 -3
- package/dist/commands/generate/unit_tests.js +3 -261
- package/dist/commands/init.js +6 -6
- package/dist/commands/tokens.js +12 -4
- package/dist/index.js +3 -6
- package/dist/managers/ChatManager.js +77 -33
- package/dist/managers/CodeManager.js +19 -5
- package/dist/managers/CommitManager.js +6 -3
- package/dist/managers/ProxyManager.js +22 -7
- package/dist/managers/ReviewManager.js +26 -10
- package/dist/managers/ShellManager.js +5 -2
- package/dist/managers/UnitTestManager.js +243 -0
- package/dist/package.json +13 -8
- package/dist/utils/command_utils.js +86 -34
- package/dist/utils/config_utils.js +15 -5
- package/dist/utils/git_utils.js +7 -4
- package/dist/utils/string_utils.js +206 -61
- package/package.json +13 -8
package/dist/commands/init.js
CHANGED
|
@@ -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(
|
|
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)}`;
|
package/dist/commands/tokens.js
CHANGED
|
@@ -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
|
|
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
|
|
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)
|
|
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(
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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(
|
|
259
|
+
this.handleList('ls *');
|
|
253
260
|
break;
|
|
254
261
|
case 'rm':
|
|
255
|
-
await this.handleRemove(
|
|
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
|
|
321
|
-
.
|
|
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
|
|
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
|
|
501
|
-
|
|
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
|
-
})
|
|
510
|
-
|
|
511
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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({
|
|
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 =
|
|
644
|
+
errorMessage = 'Invalid model or prompt size too large. Try specifying fewer files.';
|
|
602
645
|
break;
|
|
603
646
|
case 403:
|
|
604
|
-
errorMessage =
|
|
647
|
+
errorMessage = 'Inactive subscription or usage limit reached';
|
|
605
648
|
break;
|
|
606
649
|
case 429:
|
|
607
|
-
errorMessage =
|
|
650
|
+
errorMessage = 'Rate limit error';
|
|
608
651
|
break;
|
|
609
652
|
case 500:
|
|
610
|
-
errorMessage =
|
|
653
|
+
errorMessage = 'Internal server error';
|
|
611
654
|
break;
|
|
612
655
|
}
|
|
613
656
|
const promptTokens = Math.ceil(this.prompt.length / 4).toLocaleString();
|
|
614
|
-
|
|
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({
|
|
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], {
|
|
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 (
|
|
51
|
-
|
|
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
|
|
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
|
|
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)
|
|
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({
|
|
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
|
|
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
|
|
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)
|
|
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({
|
|
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
|
-
{
|
|
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
|
|
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({
|
|
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);
|