cloving 0.1.7 → 0.1.9
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/README.md +0 -2
- package/dist/cloving_gpt/adapters/claude.js +5 -5
- package/dist/cloving_gpt/adapters/openai.js +5 -5
- package/dist/cloving_gpt/index.js +48 -56
- package/dist/commands/config.js +92 -23
- package/dist/commands/generate/code.js +187 -0
- package/dist/commands/generate/commit.js +30 -5
- package/dist/commands/generate/review.js +10 -3
- package/dist/commands/generate/sh.js +95 -0
- package/dist/commands/generate/shell.js +101 -0
- package/dist/commands/init.js +20 -8
- package/dist/commands/project/build.js +13 -11
- package/dist/commands/project/init.js +13 -3
- package/dist/index.js +41 -4
- package/dist/package.json +71 -0
- package/dist/utils/command_utils.js +11 -15
- package/dist/utils/config_utils.js +5 -5
- package/dist/utils/git_utils.js +1 -22
- package/dist/utils/string_utils.js +12 -17
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
Here's the updated README.md incorporating the latest changes:
|
|
2
|
-
|
|
3
1
|
# Cloving
|
|
4
2
|
|
|
5
3
|
Cloving is an innovative CLI tool designed to seamlessly integrate AI into various aspects of the developer workflow. It enhances productivity and code quality by leveraging AI for tasks such as generating commit messages, conducting code reviews, creating unit tests, and even assisting with entire project lifecycles.
|
|
@@ -23,7 +23,7 @@ class ClaudeAdapter {
|
|
|
23
23
|
}
|
|
24
24
|
getPayload(request) {
|
|
25
25
|
return {
|
|
26
|
-
model: this.model,
|
|
26
|
+
model: this.model.replace(':', '-'),
|
|
27
27
|
system: 'You are a computer programmer giving advice on how to write better code.',
|
|
28
28
|
messages: [
|
|
29
29
|
{ role: 'user', content: request.prompt }
|
|
@@ -50,10 +50,10 @@ class ClaudeAdapter {
|
|
|
50
50
|
}
|
|
51
51
|
exports.ClaudeAdapter = ClaudeAdapter;
|
|
52
52
|
ClaudeAdapter.supportedModels = [
|
|
53
|
-
'claude:claude-3-5
|
|
54
|
-
'claude:claude-3
|
|
55
|
-
'claude:claude-3
|
|
56
|
-
'claude:claude-3
|
|
53
|
+
'claude:claude-3-5:sonnet-20240620',
|
|
54
|
+
'claude:claude-3:opus-20240229',
|
|
55
|
+
'claude:claude-3:sonnet-20240229',
|
|
56
|
+
'claude:claude-3:haiku-20240307',
|
|
57
57
|
// Add more supported models here as needed
|
|
58
58
|
];
|
|
59
59
|
// Example usage:
|
|
@@ -21,7 +21,7 @@ class OpenAIAdapter {
|
|
|
21
21
|
}
|
|
22
22
|
getPayload(request) {
|
|
23
23
|
return {
|
|
24
|
-
model: this.model,
|
|
24
|
+
model: this.model.replace(':', '-'),
|
|
25
25
|
messages: [{ role: "user", content: request.prompt }],
|
|
26
26
|
max_tokens: request.maxTokens,
|
|
27
27
|
temperature: request.temperature || 0.7
|
|
@@ -33,9 +33,9 @@ class OpenAIAdapter {
|
|
|
33
33
|
}
|
|
34
34
|
exports.OpenAIAdapter = OpenAIAdapter;
|
|
35
35
|
OpenAIAdapter.supportedModels = [
|
|
36
|
-
'openai:gpt
|
|
37
|
-
'openai:gpt
|
|
38
|
-
'openai:gpt
|
|
39
|
-
'openai:text-embedding
|
|
36
|
+
'openai:gpt:4o',
|
|
37
|
+
'openai:gpt:4-turbo',
|
|
38
|
+
'openai:gpt:3.5-turbo',
|
|
39
|
+
'openai:text-embedding:3-large',
|
|
40
40
|
// Add more supported models here as needed
|
|
41
41
|
];
|
|
@@ -4,9 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const axios_1 = __importDefault(require("axios"));
|
|
7
|
-
const readline_1 = __importDefault(require("readline"));
|
|
8
7
|
const child_process_1 = require("child_process");
|
|
9
8
|
const process_1 = __importDefault(require("process"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
10
|
const claude_1 = require("./adapters/claude");
|
|
11
11
|
const openai_1 = require("./adapters/openai");
|
|
12
12
|
const gpt4all_1 = require("./adapters/gpt4all");
|
|
@@ -22,7 +22,7 @@ class ClovingGPT {
|
|
|
22
22
|
}
|
|
23
23
|
const primaryModel = (0, config_utils_1.getPrimaryModel)(partialModel);
|
|
24
24
|
if (!primaryModel) {
|
|
25
|
-
console.log('No
|
|
25
|
+
console.log('No AI API models found in the cloving configuration. Please run: cloving config');
|
|
26
26
|
process_1.default.exit(1);
|
|
27
27
|
}
|
|
28
28
|
const { provider, model, config: modelConfig } = primaryModel;
|
|
@@ -45,68 +45,60 @@ class ClovingGPT {
|
|
|
45
45
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
async
|
|
48
|
+
async reviewPrompt(prompt, endpoint) {
|
|
49
49
|
if (this.silent)
|
|
50
|
-
return
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.error('User cancelled the operation');
|
|
78
|
-
process_1.default.exit(1);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
resolve(true);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
less.stdin.on('error', (err) => {
|
|
86
|
-
if (isNodeError(err) && err.code === 'EPIPE') {
|
|
87
|
-
resolve(true);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
console.error('Pipeline error:', err);
|
|
91
|
-
resolve(false);
|
|
50
|
+
return;
|
|
51
|
+
const tokenCount = Math.ceil(prompt.length / 4).toLocaleString();
|
|
52
|
+
const { reviewPrompt } = await inquirer_1.default.prompt([
|
|
53
|
+
{
|
|
54
|
+
type: 'confirm',
|
|
55
|
+
name: 'reviewPrompt',
|
|
56
|
+
message: `Do you want to review the ~${tokenCount} token prompt before sending it to ${endpoint}?`,
|
|
57
|
+
default: true
|
|
58
|
+
}
|
|
59
|
+
]);
|
|
60
|
+
if (reviewPrompt) {
|
|
61
|
+
const less = (0, child_process_1.spawn)('less', ['-R'], { stdio: ['pipe', process_1.default.stdout, process_1.default.stderr] });
|
|
62
|
+
less.stdin.write(prompt);
|
|
63
|
+
less.stdin.end();
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
65
|
+
less.on('close', async (code) => {
|
|
66
|
+
if (code !== 0) {
|
|
67
|
+
console.error('less command exited with an error');
|
|
68
|
+
reject(new Error('Less command failed'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const { confirmContinue } = await inquirer_1.default.prompt([
|
|
72
|
+
{
|
|
73
|
+
type: 'confirm',
|
|
74
|
+
name: 'confirmContinue',
|
|
75
|
+
message: 'Do you want to continue?',
|
|
76
|
+
default: true
|
|
92
77
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
78
|
+
]);
|
|
79
|
+
if (!confirmContinue) {
|
|
80
|
+
console.error('User cancelled the operation');
|
|
81
|
+
process_1.default.exit(1);
|
|
82
|
+
}
|
|
83
|
+
resolve();
|
|
84
|
+
});
|
|
85
|
+
less.stdin.on('error', (err) => {
|
|
86
|
+
if (isNodeError(err) && err.code === 'EPIPE') {
|
|
87
|
+
resolve();
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.error('Pipeline error:', err);
|
|
91
|
+
reject(err);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
98
94
|
});
|
|
99
|
-
}
|
|
95
|
+
}
|
|
100
96
|
}
|
|
101
97
|
async generateText(request) {
|
|
102
98
|
const endpoint = this.adapter.getEndpoint();
|
|
103
99
|
const payload = this.adapter.getPayload(request);
|
|
104
100
|
const headers = this.adapter.getHeaders(this.apiKey);
|
|
105
|
-
|
|
106
|
-
const shouldContinue = await this.askUserToConfirm(request.prompt, `Do you want to review the ~${tokenCount} token prompt before sending it to ${endpoint}? [Yn]: `);
|
|
107
|
-
if (!shouldContinue) {
|
|
108
|
-
throw 'Operation cancelled by the user.';
|
|
109
|
-
}
|
|
101
|
+
await this.reviewPrompt(request.prompt, endpoint);
|
|
110
102
|
try {
|
|
111
103
|
const response = await axios_1.default.post(endpoint, payload, { headers });
|
|
112
104
|
return this.adapter.extractResponse(response.data);
|
package/dist/commands/config.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.config = void 0;
|
|
4
7
|
const config_utils_1 = require("../utils/config_utils");
|
|
5
8
|
const command_utils_1 = require("../utils/command_utils");
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
6
10
|
const config = async () => {
|
|
7
11
|
let currentConfig = (0, config_utils_1.getConfig)({});
|
|
8
|
-
// Ensure models
|
|
12
|
+
// Ensure models are initialized properly
|
|
9
13
|
if (!currentConfig.models) {
|
|
10
14
|
currentConfig.models = {};
|
|
11
15
|
}
|
|
@@ -16,32 +20,92 @@ const config = async () => {
|
|
|
16
20
|
}
|
|
17
21
|
let continueConfig = true;
|
|
18
22
|
while (continueConfig) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const providers = [...new Set(models.map(model => model.split(':')[0]))];
|
|
24
|
+
const { selectedProvider } = await inquirer_1.default.prompt([
|
|
25
|
+
{
|
|
26
|
+
type: 'list',
|
|
27
|
+
name: 'selectedProvider',
|
|
28
|
+
message: 'Select a provider:',
|
|
29
|
+
choices: providers.map(provider => ({ name: provider, value: provider })),
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
32
|
+
const providerModels = models.filter(model => model.startsWith(selectedProvider));
|
|
33
|
+
const modelCategories = [...new Set(providerModels.map(model => model.split(':')[1]))];
|
|
34
|
+
const { selectedCategory } = await inquirer_1.default.prompt([
|
|
35
|
+
{
|
|
36
|
+
type: 'list',
|
|
37
|
+
name: 'selectedCategory',
|
|
38
|
+
message: 'Select a category:',
|
|
39
|
+
choices: modelCategories.map(category => ({ name: category, value: category })),
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
const specificModels = providerModels.filter(model => model.split(':')[1] === selectedCategory);
|
|
43
|
+
const { modelIndex } = await inquirer_1.default.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'list',
|
|
46
|
+
name: 'modelIndex',
|
|
47
|
+
message: 'Select an AI model you\'d like to use:',
|
|
48
|
+
choices: specificModels.map((model, index) => ({ name: model, value: index })),
|
|
49
|
+
pageSize: specificModels.length,
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
const selectedModel = specificModels[modelIndex];
|
|
53
|
+
const { apiKey } = await inquirer_1.default.prompt([
|
|
54
|
+
{
|
|
55
|
+
type: 'input',
|
|
56
|
+
name: 'apiKey',
|
|
57
|
+
message: `Enter your API key for ${selectedModel}: `,
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
27
60
|
const [provider, ...modelParts] = selectedModel.split(':');
|
|
28
61
|
const model = modelParts.join(':');
|
|
29
62
|
if (!currentConfig.models[provider]) {
|
|
30
63
|
currentConfig.models[provider] = {};
|
|
31
64
|
}
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
65
|
+
const { primary } = await inquirer_1.default.prompt([
|
|
66
|
+
{
|
|
67
|
+
type: 'confirm',
|
|
68
|
+
name: 'primary',
|
|
69
|
+
message: 'Set this model as the primary? Primary models are used by default if your prompt fits within its context window. You can configure backup models for larger prompts.',
|
|
70
|
+
default: true,
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
const { priority } = await inquirer_1.default.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: 'input',
|
|
76
|
+
name: 'priority',
|
|
77
|
+
message: 'Enter the priority for this model (0-100). Higher priority AI APIs are chosen as long as the prompt fits its context window:',
|
|
78
|
+
default: primary ? '100' : '0',
|
|
79
|
+
validate: (input) => {
|
|
80
|
+
const num = parseInt(input, 10);
|
|
81
|
+
if (isNaN(num) || num < 0 || num > 100) {
|
|
82
|
+
return 'Please enter a valid number between 0 and 100.';
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
const { review } = await inquirer_1.default.prompt([
|
|
89
|
+
{
|
|
90
|
+
type: 'confirm',
|
|
91
|
+
name: 'review',
|
|
92
|
+
message: `Do you want to review all prompts before they are sent to ${selectedModel}?`,
|
|
93
|
+
default: true,
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
const { trust } = await inquirer_1.default.prompt([
|
|
97
|
+
{
|
|
98
|
+
type: 'confirm',
|
|
99
|
+
name: 'trust',
|
|
100
|
+
message: `Do you trust ${selectedModel} with sensitive information?`,
|
|
101
|
+
default: false,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
40
104
|
const modelConfig = {
|
|
41
105
|
apiKey,
|
|
42
106
|
primary,
|
|
43
|
-
priority,
|
|
44
|
-
silent,
|
|
107
|
+
priority: parseInt(priority, 10),
|
|
108
|
+
silent: !review,
|
|
45
109
|
trust,
|
|
46
110
|
};
|
|
47
111
|
currentConfig.models[provider][model] = modelConfig;
|
|
@@ -55,10 +119,15 @@ const config = async () => {
|
|
|
55
119
|
}
|
|
56
120
|
}
|
|
57
121
|
}
|
|
58
|
-
const anotherModel = await
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
122
|
+
const { anotherModel } = await inquirer_1.default.prompt([
|
|
123
|
+
{
|
|
124
|
+
type: 'confirm',
|
|
125
|
+
name: 'anotherModel',
|
|
126
|
+
message: 'Do you want to configure another model?',
|
|
127
|
+
default: true,
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
continueConfig = anotherModel;
|
|
62
131
|
}
|
|
63
132
|
(0, config_utils_1.saveConfig)(currentConfig);
|
|
64
133
|
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
9
|
+
const command_utils_1 = require("../../utils/command_utils");
|
|
10
|
+
const config_utils_1 = require("../../utils/config_utils");
|
|
11
|
+
const string_utils_1 = require("../../utils/string_utils");
|
|
12
|
+
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const generateCodePrompt = (prompt, files, contextFiles, previousCode) => {
|
|
16
|
+
const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
|
|
17
|
+
const specialFiles = Object.keys(specialFileContents).map((file) => `### Contents of ${file}\n\n${JSON.stringify(specialFileContents[file], null, 2)}\n\n`).join('\n');
|
|
18
|
+
const contextFileContents = Object.keys(contextFiles).map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`).join('\n');
|
|
19
|
+
let promptText = `Here is a description of my app:
|
|
20
|
+
|
|
21
|
+
${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
|
|
22
|
+
|
|
23
|
+
${specialFiles}
|
|
24
|
+
|
|
25
|
+
${contextFileContents}
|
|
26
|
+
|
|
27
|
+
### Directory structure
|
|
28
|
+
|
|
29
|
+
${files.join('\n')}
|
|
30
|
+
|
|
31
|
+
`;
|
|
32
|
+
if (previousCode) {
|
|
33
|
+
promptText += `### Previously generated code
|
|
34
|
+
|
|
35
|
+
${previousCode}
|
|
36
|
+
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
promptText += `### Request
|
|
40
|
+
|
|
41
|
+
I would like to generate code that does the following: ${prompt}
|
|
42
|
+
|
|
43
|
+
Please generate the code and include filenames with paths to the code files mentioned and do not be lazy and ask me to keep the existing code or show things like previous code remains unchanged, always include existing code in the response.`;
|
|
44
|
+
return promptText;
|
|
45
|
+
};
|
|
46
|
+
const generateExplainCodePrompt = (prompt) => {
|
|
47
|
+
return `${prompt}
|
|
48
|
+
|
|
49
|
+
Please briefly explain how this code works.`;
|
|
50
|
+
};
|
|
51
|
+
const generateCode = async (gpt, prompt, allSrcFiles, contextFiles, previousCode) => {
|
|
52
|
+
const codePrompt = generateCodePrompt(prompt, allSrcFiles, contextFiles, previousCode);
|
|
53
|
+
return await gpt.generateText({ prompt: codePrompt });
|
|
54
|
+
};
|
|
55
|
+
const displayGeneratedCode = (rawCodeCommand) => {
|
|
56
|
+
(0, string_utils_1.parseMarkdownInstructions)(rawCodeCommand).map(code => {
|
|
57
|
+
if (code.trim().startsWith('```')) {
|
|
58
|
+
const lines = code.split('\n');
|
|
59
|
+
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
60
|
+
console.log(lines[0]);
|
|
61
|
+
console.log((0, cli_highlight_1.default)(lines.slice(1, -1).join('\n'), { language }));
|
|
62
|
+
console.log(lines.slice(-1)[0]);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log((0, cli_highlight_1.default)(code, { language: 'markdown' }));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contextFiles) => {
|
|
70
|
+
const { action } = await inquirer_1.default.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: 'list',
|
|
73
|
+
name: 'action',
|
|
74
|
+
message: 'What would you like to do?',
|
|
75
|
+
choices: [
|
|
76
|
+
{ name: 'Revise', value: 'revise' },
|
|
77
|
+
{ name: 'Explain', value: 'explain' },
|
|
78
|
+
{ name: 'Copy Source Code to Clipboard', value: 'copySource' },
|
|
79
|
+
{ name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
|
|
80
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
switch (action) {
|
|
85
|
+
case 'revise':
|
|
86
|
+
const { newPrompt } = await inquirer_1.default.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: 'input',
|
|
89
|
+
name: 'newPrompt',
|
|
90
|
+
message: 'Enter your revised prompt:',
|
|
91
|
+
},
|
|
92
|
+
]);
|
|
93
|
+
const newRawCodeCommand = await generateCode(gpt, newPrompt, allSrcFiles, contextFiles, rawCodeCommand);
|
|
94
|
+
displayGeneratedCode(newRawCodeCommand);
|
|
95
|
+
await handleUserAction(gpt, newRawCodeCommand, newPrompt, allSrcFiles, contextFiles);
|
|
96
|
+
break;
|
|
97
|
+
case 'explain':
|
|
98
|
+
const explainPrompt = generateExplainCodePrompt(rawCodeCommand);
|
|
99
|
+
const explainCodeCommand = await gpt.generateText({ prompt: explainPrompt });
|
|
100
|
+
console.log((0, cli_highlight_1.default)(explainCodeCommand, { language: 'markdown' }));
|
|
101
|
+
break;
|
|
102
|
+
case 'copySource':
|
|
103
|
+
const sourceCode = (0, string_utils_1.extractMarkdown)(rawCodeCommand);
|
|
104
|
+
copy_paste_1.default.copy(sourceCode, () => {
|
|
105
|
+
console.log('Source code to clipboard');
|
|
106
|
+
});
|
|
107
|
+
break;
|
|
108
|
+
case 'copyAll':
|
|
109
|
+
copy_paste_1.default.copy(rawCodeCommand, () => {
|
|
110
|
+
console.log('Entire response copied to clipboard');
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
case 'cancel':
|
|
114
|
+
console.log('Operation cancelled.');
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const code = async (options) => {
|
|
119
|
+
let { prompt, files } = options;
|
|
120
|
+
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
121
|
+
const gpt = new cloving_gpt_1.default(options);
|
|
122
|
+
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, true);
|
|
123
|
+
const contextFiles = {};
|
|
124
|
+
try {
|
|
125
|
+
if (!prompt) {
|
|
126
|
+
const { userPrompt } = await inquirer_1.default.prompt([
|
|
127
|
+
{
|
|
128
|
+
type: 'input',
|
|
129
|
+
name: 'userPrompt',
|
|
130
|
+
message: 'What would you like to build:',
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
prompt = userPrompt;
|
|
134
|
+
}
|
|
135
|
+
if (files && files.length > 0) {
|
|
136
|
+
// Check if the files exist
|
|
137
|
+
const nonExistentFiles = files.filter(file => !fs_1.default.existsSync(file));
|
|
138
|
+
if (nonExistentFiles.length > 0) {
|
|
139
|
+
throw new Error(`The following files do not exist: ${nonExistentFiles.join(', ')}`);
|
|
140
|
+
}
|
|
141
|
+
// Read the content of the provided files
|
|
142
|
+
for (const file of files) {
|
|
143
|
+
const content = fs_1.default.readFileSync(file, 'utf-8');
|
|
144
|
+
contextFiles[file] = content;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Prompt for additional context files if no files were passed in options
|
|
149
|
+
let includeMoreFiles = true;
|
|
150
|
+
while (includeMoreFiles) {
|
|
151
|
+
const { contextFile } = await inquirer_1.default.prompt([
|
|
152
|
+
{
|
|
153
|
+
type: 'input',
|
|
154
|
+
name: 'contextFile',
|
|
155
|
+
message: 'Relative path of an existing file you would like to include as context in the prompt [optional]:',
|
|
156
|
+
},
|
|
157
|
+
]);
|
|
158
|
+
if (contextFile) {
|
|
159
|
+
const filePath = path_1.default.resolve(contextFile);
|
|
160
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
161
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
162
|
+
contextFiles[filePath] = content;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.log(`File ${contextFile} does not exist.`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const { includeMore } = await inquirer_1.default.prompt([
|
|
169
|
+
{
|
|
170
|
+
type: 'confirm',
|
|
171
|
+
name: 'includeMore',
|
|
172
|
+
message: 'Do you want to include any other files?',
|
|
173
|
+
default: false,
|
|
174
|
+
},
|
|
175
|
+
]);
|
|
176
|
+
includeMoreFiles = includeMore;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const rawCodeCommand = await generateCode(gpt, prompt, allSrcFiles, contextFiles);
|
|
180
|
+
displayGeneratedCode(rawCodeCommand);
|
|
181
|
+
await handleUserAction(gpt, rawCodeCommand, prompt, allSrcFiles, contextFiles);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
console.error('Could not generate code', error);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
exports.default = code;
|
|
@@ -6,16 +6,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
|
-
const git_utils_1 = require("../../utils/git_utils");
|
|
10
9
|
const string_utils_1 = require("../../utils/string_utils");
|
|
11
10
|
const config_utils_1 = require("../../utils/config_utils");
|
|
12
11
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
13
|
-
const
|
|
12
|
+
const generateCommitMessagePrompt = (diff) => {
|
|
13
|
+
return `Generate a concise and meaningful commit message based on a diff.
|
|
14
|
+
|
|
15
|
+
Do not add any commentary or context to the message other than the commit message itself.
|
|
16
|
+
|
|
17
|
+
An example of the output for this should look like the following:
|
|
18
|
+
|
|
19
|
+
\`\`\`plaintext
|
|
20
|
+
Update dependencies and package versions
|
|
21
|
+
|
|
22
|
+
- Upgrade Ruby gems including aws-sdk, honeybadger, irb, and rubocop
|
|
23
|
+
- Update Node.js packages including esbuild, tinymce, and trix
|
|
24
|
+
- Bump TypeScript and ESLint related packages to latest versions
|
|
25
|
+
\`\`\`
|
|
26
|
+
|
|
27
|
+
Here is the diff to help you write the commit message:
|
|
28
|
+
|
|
29
|
+
${diff}`;
|
|
30
|
+
};
|
|
31
|
+
const commit = async (options) => {
|
|
14
32
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
15
33
|
const gpt = new cloving_gpt_1.default(options);
|
|
16
34
|
try {
|
|
35
|
+
// Get the git diff
|
|
36
|
+
const diff = (0, child_process_1.execSync)('git diff HEAD').toString().trim();
|
|
37
|
+
// Check if the diff is blank
|
|
38
|
+
if (!diff) {
|
|
39
|
+
console.error('No changes to commit.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
17
42
|
// Generate the prompt for commit message
|
|
18
|
-
const prompt =
|
|
43
|
+
const prompt = generateCommitMessagePrompt(diff);
|
|
19
44
|
// Instantiate ClovingGPT and get the commit message
|
|
20
45
|
const rawCommitMessage = await gpt.generateText({ prompt });
|
|
21
46
|
// Clean the commit message using extractMarkdown
|
|
@@ -38,7 +63,7 @@ const generateAndCommitMessage = async (options) => {
|
|
|
38
63
|
});
|
|
39
64
|
}
|
|
40
65
|
catch (error) {
|
|
41
|
-
console.error('Could not generate commit message');
|
|
66
|
+
console.error('Could not generate commit message:', error);
|
|
42
67
|
}
|
|
43
68
|
};
|
|
44
|
-
exports.default =
|
|
69
|
+
exports.default = commit;
|
|
@@ -5,12 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const child_process_1 = require("child_process");
|
|
7
7
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
8
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
9
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
9
10
|
const git_utils_1 = require("../../utils/git_utils");
|
|
10
11
|
const string_utils_1 = require("../../utils/string_utils");
|
|
11
12
|
const config_utils_1 = require("../../utils/config_utils");
|
|
12
13
|
const string_utils_2 = require("../../utils/string_utils");
|
|
13
|
-
const command_utils_1 = require("../../utils/command_utils");
|
|
14
14
|
const review = async (options) => {
|
|
15
15
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
16
16
|
const gpt = new cloving_gpt_1.default(options);
|
|
@@ -39,8 +39,15 @@ ${gitDiff}`;
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
// Prompt the user to copy the analysis to the clipboard
|
|
42
|
-
const
|
|
43
|
-
|
|
42
|
+
const { copyToClipboard } = await inquirer_1.default.prompt([
|
|
43
|
+
{
|
|
44
|
+
type: 'confirm',
|
|
45
|
+
name: 'copyToClipboard',
|
|
46
|
+
message: 'Do you want to copy the analysis to the clipboard?',
|
|
47
|
+
default: true
|
|
48
|
+
}
|
|
49
|
+
]);
|
|
50
|
+
if (copyToClipboard) {
|
|
44
51
|
try {
|
|
45
52
|
(0, child_process_1.execFileSync)('pbcopy', { input: markdown });
|
|
46
53
|
console.log('Analysis copied to clipboard');
|