cloving 0.1.0 → 0.1.1-8.1
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 +168 -40
- package/dist/cloving_gpt/adapters/claude.js +6 -8
- package/dist/cloving_gpt/adapters/gemini.js +62 -0
- package/dist/cloving_gpt/adapters/mistral.js +46 -0
- package/dist/cloving_gpt/adapters/openai.js +7 -6
- package/dist/cloving_gpt/index.js +64 -63
- package/dist/commands/commit.js +2 -2
- package/dist/commands/config.js +86 -19
- package/dist/commands/generate/code.js +337 -0
- package/dist/commands/generate/commit.js +69 -0
- package/dist/commands/generate/context.js +75 -0
- package/dist/commands/generate/fizzbuzz.js +35 -0
- package/dist/commands/generate/review.js +146 -0
- package/dist/commands/generate/sh.js +95 -0
- package/dist/commands/generate/shell.js +111 -0
- package/dist/commands/generate/unit_tests.js +314 -0
- package/dist/commands/init.js +86 -33
- package/dist/commands/models.js +5 -2
- package/dist/commands/project/build.js +88 -0
- package/dist/commands/project/complete.js +12 -0
- package/dist/commands/project/init.js +82 -0
- package/dist/commands/project/plan.js +108 -0
- package/dist/commands/review.js +2 -2
- package/dist/commands/unit_tests.js +108 -77
- package/dist/index.js +111 -21
- package/dist/package.json +76 -0
- package/dist/utils/command_utils.js +36 -70
- package/dist/utils/config_utils.js +170 -0
- package/dist/utils/git_utils.js +37 -44
- package/dist/utils/string_utils.js +72 -18
- package/package.json +28 -17
package/dist/commands/config.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.config = void 0;
|
|
4
|
+
const config_utils_1 = require("../utils/config_utils");
|
|
4
5
|
const command_utils_1 = require("../utils/command_utils");
|
|
6
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
5
7
|
const config = async () => {
|
|
6
|
-
let currentConfig = (0,
|
|
7
|
-
// Ensure models
|
|
8
|
+
let currentConfig = (0, config_utils_1.getConfig)({});
|
|
9
|
+
// Ensure models are initialized properly
|
|
8
10
|
if (!currentConfig.models) {
|
|
9
11
|
currentConfig.models = {};
|
|
10
12
|
}
|
|
@@ -15,26 +17,91 @@ const config = async () => {
|
|
|
15
17
|
}
|
|
16
18
|
let continueConfig = true;
|
|
17
19
|
while (continueConfig) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const providers = [...new Set(models.map(model => model.split(':')[0]))];
|
|
21
|
+
const selectedProvider = await (0, prompts_1.select)({
|
|
22
|
+
message: 'Select a provider:',
|
|
23
|
+
choices: providers.map(provider => ({ name: provider, value: provider })),
|
|
24
|
+
});
|
|
25
|
+
const providerModels = models.filter(model => model.startsWith(selectedProvider));
|
|
26
|
+
const modelCategories = [...new Set(providerModels.map(model => model.split(':')[1]))];
|
|
27
|
+
const selectedCategory = await (0, prompts_1.select)({
|
|
28
|
+
message: 'Select a category:',
|
|
29
|
+
choices: modelCategories.map(category => ({ name: category, value: category })),
|
|
30
|
+
});
|
|
31
|
+
const specificModels = providerModels.filter(model => model.split(':')[1] === selectedCategory);
|
|
32
|
+
const modelIndex = await (0, prompts_1.select)({
|
|
33
|
+
message: 'Select an AI model you\'d like to use:',
|
|
34
|
+
choices: specificModels.map((model, index) => ({ name: model, value: index })),
|
|
35
|
+
pageSize: specificModels.length,
|
|
36
|
+
});
|
|
37
|
+
const selectedModel = specificModels[modelIndex];
|
|
38
|
+
const apiKey = await (0, prompts_1.input)({
|
|
39
|
+
message: `Enter your API key for ${selectedModel}: `,
|
|
40
|
+
});
|
|
41
|
+
const [provider, ...modelParts] = selectedModel?.split(':') || [];
|
|
42
|
+
const model = modelParts.join(':');
|
|
43
|
+
if (!currentConfig.models[provider]) {
|
|
44
|
+
currentConfig.models[provider] = {};
|
|
24
45
|
}
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
const primary = await (0, prompts_1.confirm)({
|
|
47
|
+
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.',
|
|
48
|
+
default: true,
|
|
49
|
+
});
|
|
50
|
+
const priority = await (0, prompts_1.input)({
|
|
51
|
+
message: 'Enter the priority for this model (0-100). Higher priority AI APIs are chosen as long as the prompt fits its context window:',
|
|
52
|
+
default: primary ? '100' : '0',
|
|
53
|
+
validate: (input) => {
|
|
54
|
+
const num = parseInt(input, 10);
|
|
55
|
+
if (isNaN(num) || num < 0 || num > 100) {
|
|
56
|
+
return 'Please enter a valid number between 0 and 100.';
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const review = await (0, prompts_1.confirm)({
|
|
62
|
+
message: `Do you want to review all prompts before they are sent to ${selectedModel}?`,
|
|
63
|
+
default: true,
|
|
64
|
+
});
|
|
65
|
+
const trust = await (0, prompts_1.confirm)({
|
|
66
|
+
message: `Do you trust ${selectedModel} with sensitive information?`,
|
|
67
|
+
default: false,
|
|
68
|
+
});
|
|
69
|
+
const temperature = await (0, prompts_1.number)({
|
|
70
|
+
message: `Enter the temperature for ${selectedModel} (0.0 - 1.0): [0.2]`,
|
|
71
|
+
default: 0.2,
|
|
72
|
+
validate: (value) => {
|
|
73
|
+
if (value && (isNaN(value) || value < 0 || value > 1)) {
|
|
74
|
+
return 'Please enter a valid number between 0.0 and 1.0.';
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
},
|
|
78
|
+
}) || 0.2;
|
|
79
|
+
const modelConfig = {
|
|
80
|
+
apiKey,
|
|
81
|
+
primary,
|
|
82
|
+
priority: parseInt(priority, 10),
|
|
83
|
+
silent: !review,
|
|
84
|
+
trust,
|
|
85
|
+
temperature,
|
|
86
|
+
};
|
|
87
|
+
currentConfig.models[provider][model] = modelConfig;
|
|
88
|
+
if (primary) {
|
|
89
|
+
// Ensure only one primary model
|
|
90
|
+
for (const p in currentConfig.models) {
|
|
91
|
+
for (const m in currentConfig.models[p]) {
|
|
92
|
+
if (p !== provider || m !== model) {
|
|
93
|
+
currentConfig.models[p][m].primary = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
35
97
|
}
|
|
98
|
+
const anotherModel = await (0, prompts_1.confirm)({
|
|
99
|
+
message: 'Do you want to configure another model?',
|
|
100
|
+
default: true,
|
|
101
|
+
});
|
|
102
|
+
continueConfig = anotherModel;
|
|
36
103
|
}
|
|
37
|
-
(0,
|
|
104
|
+
(0, config_utils_1.saveConfig)(currentConfig);
|
|
38
105
|
};
|
|
39
106
|
exports.config = config;
|
|
40
107
|
exports.default = exports.config;
|
|
@@ -0,0 +1,337 @@
|
|
|
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 prompts_1 = require("@inquirer/prompts");
|
|
8
|
+
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const command_utils_1 = require("../../utils/command_utils");
|
|
12
|
+
const config_utils_1 = require("../../utils/config_utils");
|
|
13
|
+
const string_utils_1 = require("../../utils/string_utils");
|
|
14
|
+
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
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 = `### Description of 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 += `### Example of a well-structured response
|
|
40
|
+
|
|
41
|
+
Here are the files that I would like to generate:
|
|
42
|
+
|
|
43
|
+
1. **src/commands/generate/shell.ts**
|
|
44
|
+
|
|
45
|
+
\`\`\`typescript
|
|
46
|
+
import inquirer from 'inquirer'
|
|
47
|
+
import highlight from 'cli-highlight'
|
|
48
|
+
import { execSync } from 'child_process'
|
|
49
|
+
import ncp from 'copy-paste'
|
|
50
|
+
import { extractMarkdown } from '../../utils/string_utils'
|
|
51
|
+
import { getConfig } from '../../utils/config_utils'
|
|
52
|
+
import ClovingGPT from '../../cloving_gpt'
|
|
53
|
+
import type { ClovingGPTOptions } from '../../utils/types'
|
|
54
|
+
|
|
55
|
+
const generateShellPrompt = (prompt: string | undefined): string => {
|
|
56
|
+
const shell = execSync('echo $SHELL').toString().trim()
|
|
57
|
+
const os = execSync('echo $OSTYPE').toString().trim()
|
|
58
|
+
return \`Generate an executable \${shell} script that works on \${os}. Try to make it a single line if possible and as simple and straightforward as possible.
|
|
59
|
+
|
|
60
|
+
Do not add any commentary or context to the message other than the commit message itself.
|
|
61
|
+
|
|
62
|
+
An example of the output for this should look like the following:
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
### Request
|
|
66
|
+
|
|
67
|
+
Generate code that does the following: ${prompt}
|
|
68
|
+
|
|
69
|
+
Do not use any data from the example response structure, only use the structure. 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.`;
|
|
70
|
+
return promptText;
|
|
71
|
+
};
|
|
72
|
+
const generateExplainCodePrompt = (prompt) => {
|
|
73
|
+
return `${prompt}
|
|
74
|
+
|
|
75
|
+
## Task
|
|
76
|
+
|
|
77
|
+
Please briefly explain how the code works in this.`;
|
|
78
|
+
};
|
|
79
|
+
const generateCode = async (gpt, prompt, allSrcFiles, contextFiles, previousCode) => {
|
|
80
|
+
const codePrompt = generateCodePrompt(prompt, allSrcFiles, contextFiles, previousCode);
|
|
81
|
+
return await gpt.generateText({ prompt: codePrompt });
|
|
82
|
+
};
|
|
83
|
+
const displayGeneratedCode = (rawCodeCommand) => {
|
|
84
|
+
(0, string_utils_1.parseMarkdownInstructions)(rawCodeCommand).map(code => {
|
|
85
|
+
if (code.trim().startsWith('```')) {
|
|
86
|
+
const lines = code.split('\n');
|
|
87
|
+
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
88
|
+
console.log(lines[0]);
|
|
89
|
+
try {
|
|
90
|
+
console.log((0, cli_highlight_1.default)(lines.slice(1, -1).join('\n'), { language }));
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
// don't highlight if it fails
|
|
94
|
+
console.log(lines.slice(1, -1).join('\n'));
|
|
95
|
+
}
|
|
96
|
+
console.log(lines.slice(-1)[0]);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log((0, cli_highlight_1.default)(code, { language: 'markdown' }));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
const saveGeneratedFiles = async (files, fileContents) => {
|
|
104
|
+
for (const file of files) {
|
|
105
|
+
if (fileContents[file]) {
|
|
106
|
+
const filePath = path_1.default.resolve(file);
|
|
107
|
+
await fs_1.default.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
108
|
+
await fs_1.default.promises.writeFile(filePath, fileContents[file]);
|
|
109
|
+
console.log(`${file} has been saved.`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log(`File content not found for ${file}.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const updateContextFiles = async (contextFiles, files, fileContents) => {
|
|
117
|
+
for (const file of files) {
|
|
118
|
+
if (fileContents[file]) {
|
|
119
|
+
contextFiles[file] = fileContents[file];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contextFiles, options) => {
|
|
124
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
125
|
+
if (options.save) {
|
|
126
|
+
await saveGeneratedFiles(files, fileContents);
|
|
127
|
+
await updateContextFiles(contextFiles, files, fileContents);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const action = await (0, prompts_1.select)({
|
|
131
|
+
message: 'What would you like to do?',
|
|
132
|
+
choices: [
|
|
133
|
+
{ name: 'Revise', value: 'revise' },
|
|
134
|
+
{ name: 'Explain', value: 'explain' },
|
|
135
|
+
{ name: 'Save a Source Code File', value: 'save' },
|
|
136
|
+
{ name: 'Save All Source Code Files', value: 'saveAll' },
|
|
137
|
+
{ name: 'Copy Source Code to Clipboard', value: 'copySource' },
|
|
138
|
+
{ name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
|
|
139
|
+
{ name: 'Done', value: 'done' },
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
switch (action) {
|
|
143
|
+
case 'revise':
|
|
144
|
+
const newPrompt = await (0, prompts_1.input)({
|
|
145
|
+
message: 'How would you like to modify the output:',
|
|
146
|
+
});
|
|
147
|
+
let includeMoreFiles = true;
|
|
148
|
+
while (includeMoreFiles) {
|
|
149
|
+
const contextFile = await (0, prompts_1.input)({
|
|
150
|
+
message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
151
|
+
});
|
|
152
|
+
if (contextFile) {
|
|
153
|
+
contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, contextFiles, options);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
includeMoreFiles = false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const newRawCodeCommand = await generateCode(gpt, newPrompt, allSrcFiles, contextFiles, rawCodeCommand);
|
|
160
|
+
displayGeneratedCode(newRawCodeCommand);
|
|
161
|
+
await handleUserAction(gpt, newRawCodeCommand, newPrompt, allSrcFiles, contextFiles, options);
|
|
162
|
+
break;
|
|
163
|
+
case 'explain':
|
|
164
|
+
const explainPrompt = generateExplainCodePrompt(rawCodeCommand);
|
|
165
|
+
const explainCodeCommand = await gpt.generateText({ prompt: explainPrompt });
|
|
166
|
+
console.log((0, cli_highlight_1.default)(explainCodeCommand, { language: 'markdown' }));
|
|
167
|
+
break;
|
|
168
|
+
case 'copySource':
|
|
169
|
+
let copyAnother = true;
|
|
170
|
+
while (copyAnother) {
|
|
171
|
+
const fileToCopy = await (0, prompts_1.select)({
|
|
172
|
+
message: 'Which file do you want to copy to the clipboard?',
|
|
173
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
174
|
+
});
|
|
175
|
+
if (fileContents[fileToCopy]) {
|
|
176
|
+
copy_paste_1.default.copy(fileContents[fileToCopy], () => {
|
|
177
|
+
console.log(`${fileToCopy} copied to clipboard`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log('File content not found.');
|
|
182
|
+
}
|
|
183
|
+
const copyMore = await (0, prompts_1.confirm)({
|
|
184
|
+
message: 'Do you want to copy another file?',
|
|
185
|
+
default: true,
|
|
186
|
+
});
|
|
187
|
+
copyAnother = copyMore;
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
case 'copyAll':
|
|
191
|
+
copy_paste_1.default.copy(rawCodeCommand, () => {
|
|
192
|
+
console.log('Entire response copied to clipboard');
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
case 'save':
|
|
196
|
+
let saveAnother = true;
|
|
197
|
+
while (saveAnother) {
|
|
198
|
+
const fileToSave = await (0, prompts_1.select)({
|
|
199
|
+
message: 'Which file do you want to save?',
|
|
200
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
201
|
+
});
|
|
202
|
+
if (fileContents[fileToSave]) {
|
|
203
|
+
const filePath = path_1.default.resolve(fileToSave);
|
|
204
|
+
await fs_1.default.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
205
|
+
await fs_1.default.promises.writeFile(filePath, fileContents[fileToSave]);
|
|
206
|
+
console.log(`${fileToSave} has been saved.`);
|
|
207
|
+
await updateContextFiles(contextFiles, [fileToSave], fileContents);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
console.log('File content not found.');
|
|
211
|
+
}
|
|
212
|
+
const saveMore = await (0, prompts_1.confirm)({
|
|
213
|
+
message: 'Do you want to save another file?',
|
|
214
|
+
default: true,
|
|
215
|
+
});
|
|
216
|
+
saveAnother = saveMore;
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
case 'saveAll':
|
|
220
|
+
await saveGeneratedFiles(files, fileContents);
|
|
221
|
+
await updateContextFiles(contextFiles, files, fileContents);
|
|
222
|
+
console.log('All files have been saved.');
|
|
223
|
+
break;
|
|
224
|
+
case 'done':
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const getAllFilesInDirectory = async (dir) => {
|
|
229
|
+
const subdirs = await fs_1.default.promises.readdir(dir);
|
|
230
|
+
const files = await Promise.all(subdirs.map(async (subdir) => {
|
|
231
|
+
const res = path_1.default.resolve(dir, subdir);
|
|
232
|
+
if (subdir === 'node_modules' || subdir === '.git') {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
return (await fs_1.default.promises.stat(res)).isDirectory() ? getAllFilesInDirectory(res) : res;
|
|
236
|
+
}));
|
|
237
|
+
return files.flat();
|
|
238
|
+
};
|
|
239
|
+
const code = async (options) => {
|
|
240
|
+
let { prompt, files } = options;
|
|
241
|
+
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
242
|
+
const gpt = new cloving_gpt_1.default(options);
|
|
243
|
+
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, false);
|
|
244
|
+
let contextFiles = {};
|
|
245
|
+
try {
|
|
246
|
+
if (files) {
|
|
247
|
+
let expandedFiles = [];
|
|
248
|
+
for (const file of files) {
|
|
249
|
+
const filePath = path_1.default.resolve(file);
|
|
250
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
|
|
251
|
+
const dirFiles = await getAllFilesInDirectory(filePath);
|
|
252
|
+
expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
files = expandedFiles;
|
|
259
|
+
for (const file of files) {
|
|
260
|
+
const filePath = path_1.default.resolve(file);
|
|
261
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
262
|
+
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
263
|
+
contextFiles[file] = content;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
let includeMoreFiles = true;
|
|
269
|
+
while (includeMoreFiles) {
|
|
270
|
+
const contextFile = await (0, prompts_1.input)({
|
|
271
|
+
message: `Enter the relative path of a file or directory you would like to include as context (or press enter to continue):`,
|
|
272
|
+
});
|
|
273
|
+
if (contextFile) {
|
|
274
|
+
contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, contextFiles, options);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
includeMoreFiles = false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!prompt) {
|
|
282
|
+
const userPrompt = await (0, prompts_1.input)({
|
|
283
|
+
message: 'What would you like the code to do:',
|
|
284
|
+
});
|
|
285
|
+
prompt = userPrompt;
|
|
286
|
+
}
|
|
287
|
+
let rawCodeCommand = await generateCode(gpt, prompt, allSrcFiles, contextFiles);
|
|
288
|
+
displayGeneratedCode(rawCodeCommand);
|
|
289
|
+
if (options.save) {
|
|
290
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
291
|
+
await saveGeneratedFiles(files, fileContents);
|
|
292
|
+
await updateContextFiles(contextFiles, files, fileContents);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
await handleUserAction(gpt, rawCodeCommand, prompt, allSrcFiles, contextFiles, options);
|
|
296
|
+
}
|
|
297
|
+
if (options.interactive) {
|
|
298
|
+
let continueInteractive = true;
|
|
299
|
+
while (continueInteractive) {
|
|
300
|
+
const newPrompt = await (0, prompts_1.input)({
|
|
301
|
+
message: 'Revise the code (or press enter to finish):',
|
|
302
|
+
});
|
|
303
|
+
if (newPrompt.trim() === '') {
|
|
304
|
+
continueInteractive = false;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
let includeMoreFiles = true;
|
|
308
|
+
while (includeMoreFiles) {
|
|
309
|
+
const contextFile = await (0, prompts_1.input)({
|
|
310
|
+
message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
311
|
+
});
|
|
312
|
+
if (contextFile) {
|
|
313
|
+
contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, contextFiles, options);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
includeMoreFiles = false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
rawCodeCommand = await generateCode(gpt, newPrompt, allSrcFiles, contextFiles, rawCodeCommand);
|
|
320
|
+
displayGeneratedCode(rawCodeCommand);
|
|
321
|
+
if (options.save || options.interactive) {
|
|
322
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
323
|
+
await saveGeneratedFiles(files, fileContents);
|
|
324
|
+
await updateContextFiles(contextFiles, files, fileContents);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
await handleUserAction(gpt, rawCodeCommand, newPrompt, allSrcFiles, contextFiles, options);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
console.error('Could not generate code', error);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
exports.default = code;
|
|
@@ -0,0 +1,69 @@
|
|
|
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 fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const string_utils_1 = require("../../utils/string_utils");
|
|
10
|
+
const config_utils_1 = require("../../utils/config_utils");
|
|
11
|
+
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
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) => {
|
|
32
|
+
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
33
|
+
const gpt = new cloving_gpt_1.default(options);
|
|
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
|
+
}
|
|
42
|
+
// Generate the prompt for commit message
|
|
43
|
+
const prompt = generateCommitMessagePrompt(diff);
|
|
44
|
+
// Instantiate ClovingGPT and get the commit message
|
|
45
|
+
const rawCommitMessage = await gpt.generateText({ prompt });
|
|
46
|
+
// Clean the commit message using extractMarkdown
|
|
47
|
+
const commitMessage = (0, string_utils_1.extractMarkdown)(rawCommitMessage);
|
|
48
|
+
// Write the commit message to a temporary file
|
|
49
|
+
const tempCommitFilePath = path_1.default.join('.git', 'SUGGESTED_COMMIT_EDITMSG');
|
|
50
|
+
fs_1.default.writeFileSync(tempCommitFilePath, commitMessage);
|
|
51
|
+
// Commit the changes using the generated commit message
|
|
52
|
+
try {
|
|
53
|
+
(0, child_process_1.execFileSync)('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], { stdio: 'inherit' });
|
|
54
|
+
}
|
|
55
|
+
catch (commitError) {
|
|
56
|
+
// If commit is canceled (non-zero exit), handle it here
|
|
57
|
+
console.log('Commit was canceled or failed.');
|
|
58
|
+
}
|
|
59
|
+
// Remove the temporary file using fs
|
|
60
|
+
fs_1.default.unlink(tempCommitFilePath, (err) => {
|
|
61
|
+
if (err)
|
|
62
|
+
throw err;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error('Could not generate commit message:', error);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
exports.default = commit;
|
|
@@ -0,0 +1,75 @@
|
|
|
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 cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
7
|
+
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
11
|
+
const command_utils_1 = require("../../utils/command_utils");
|
|
12
|
+
const config_utils_1 = require("../../utils/config_utils");
|
|
13
|
+
const generateContextPrompt = (files, contextFiles) => {
|
|
14
|
+
const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
|
|
15
|
+
const specialFiles = Object.keys(specialFileContents).map((file) => `### Contents of ${file}\n\n${JSON.stringify(specialFileContents[file], null, 2)}\n\n`).join('\n');
|
|
16
|
+
const contextFileContents = Object.keys(contextFiles).map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`).join('\n');
|
|
17
|
+
return `### Description of App
|
|
18
|
+
|
|
19
|
+
${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
|
|
20
|
+
|
|
21
|
+
${specialFiles}
|
|
22
|
+
|
|
23
|
+
${contextFileContents}
|
|
24
|
+
|
|
25
|
+
### Directory structure
|
|
26
|
+
|
|
27
|
+
${files.join('\n')}`;
|
|
28
|
+
};
|
|
29
|
+
const context = async (options) => {
|
|
30
|
+
let { files } = options;
|
|
31
|
+
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
32
|
+
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, false);
|
|
33
|
+
let contextFiles = {};
|
|
34
|
+
try {
|
|
35
|
+
if (files) {
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const filePath = path_1.default.resolve(file);
|
|
38
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
39
|
+
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
40
|
+
contextFiles[file] = content;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
let includeMoreFiles = true;
|
|
46
|
+
while (includeMoreFiles) {
|
|
47
|
+
const contextFile = await (0, prompts_1.input)({
|
|
48
|
+
message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
49
|
+
});
|
|
50
|
+
if (contextFile) {
|
|
51
|
+
contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, contextFiles, options);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
includeMoreFiles = false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const contextPrompt = generateContextPrompt(allSrcFiles, contextFiles);
|
|
59
|
+
console.log('\nGenerated Context Prompt:');
|
|
60
|
+
console.log((0, cli_highlight_1.default)(contextPrompt, { language: 'markdown' }));
|
|
61
|
+
const copyToClipboard = await (0, prompts_1.confirm)({
|
|
62
|
+
message: 'Do you want to copy the context prompt to your clipboard?',
|
|
63
|
+
default: true,
|
|
64
|
+
});
|
|
65
|
+
if (copyToClipboard) {
|
|
66
|
+
copy_paste_1.default.copy(contextPrompt, () => {
|
|
67
|
+
console.log('Context prompt copied to clipboard');
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error('Could not generate context', error);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
exports.default = context;
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
exports.fizzbuzzCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
|
+
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
10
|
+
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
11
|
+
const fizzbuzz_utils_1 = require("../../utils/fizzbuzz_utils");
|
|
12
|
+
exports.fizzbuzzCommand = new commander_1.Command('fizzbuzz')
|
|
13
|
+
.description('Generate FizzBuzz sequence')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const answers = await inquirer_1.default.prompt([
|
|
16
|
+
{
|
|
17
|
+
type: 'number',
|
|
18
|
+
name: 'start',
|
|
19
|
+
message: 'Enter the starting number:',
|
|
20
|
+
default: 1,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'number',
|
|
24
|
+
name: 'end',
|
|
25
|
+
message: 'Enter the ending number:',
|
|
26
|
+
default: 100,
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
const fizzBuzzArray = (0, fizzbuzz_utils_1.generateFizzBuzzArray)(answers.start, answers.end);
|
|
30
|
+
const output = fizzBuzzArray.join('\n');
|
|
31
|
+
console.log((0, cli_highlight_1.default)(output, { language: 'plaintext', ignoreIllegals: true }));
|
|
32
|
+
copy_paste_1.default.copy(output, () => {
|
|
33
|
+
console.log('FizzBuzz sequence copied to clipboard!');
|
|
34
|
+
});
|
|
35
|
+
});
|