cloving 0.1.20 → 0.1.22
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 +29 -2
- package/dist/cloving_gpt/adapters/gemini.js +34 -4
- package/dist/cloving_gpt/adapters/mistral.js +45 -2
- package/dist/cloving_gpt/adapters/ollama.js +34 -2
- package/dist/cloving_gpt/adapters/openai.js +46 -2
- package/dist/cloving_gpt/index.js +52 -6
- package/dist/commands/chat.js +14 -0
- package/dist/commands/generate/code.js +8 -312
- package/dist/commands/generate/commit.js +2 -20
- package/dist/commands/generate/review.js +12 -1
- package/dist/commands/proxy.js +11 -0
- package/dist/commands/tokens.js +43 -0
- package/dist/index.js +23 -0
- package/dist/managers/ChatManager.js +283 -0
- package/dist/managers/ChunkManager.js +20 -0
- package/dist/managers/CodeManager.js +319 -0
- package/dist/managers/ProxyManager.js +163 -0
- package/dist/managers/src/managers/ProxyManager.js +1 -0
- package/dist/package.json +1 -0
- package/dist/utils/command_utils.js +11 -3
- package/dist/utils/git_utils.js +21 -1
- package/dist/utils/string_utils.js +21 -2
- package/package.json +5 -1
|
@@ -0,0 +1,283 @@
|
|
|
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 path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const readline_1 = __importDefault(require("readline"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const ChunkManager_1 = __importDefault(require("./ChunkManager"));
|
|
12
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
13
|
+
const git_utils_1 = require("../utils/git_utils");
|
|
14
|
+
const string_utils_1 = require("../utils/string_utils");
|
|
15
|
+
const command_utils_1 = require("../utils/command_utils");
|
|
16
|
+
const PREAMBLE = `Don't apologize and when generating code, always include filenames in bold 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.`;
|
|
17
|
+
class ChatManager {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.options = options;
|
|
20
|
+
this.chatHistory = [];
|
|
21
|
+
this.commandHistory = [];
|
|
22
|
+
this.historyIndex = -1;
|
|
23
|
+
this.multilineInput = '';
|
|
24
|
+
this.isMultilineMode = false;
|
|
25
|
+
this.contextFiles = {};
|
|
26
|
+
this.prompt = '';
|
|
27
|
+
options.stream = true;
|
|
28
|
+
options.silent = true;
|
|
29
|
+
this.gpt = new cloving_gpt_1.default(options);
|
|
30
|
+
this.rl = readline_1.default.createInterface({
|
|
31
|
+
input: process.stdin,
|
|
32
|
+
output: process.stdout,
|
|
33
|
+
prompt: 'cloving> ',
|
|
34
|
+
historySize: 1000,
|
|
35
|
+
});
|
|
36
|
+
this.chunkManager = new ChunkManager_1.default();
|
|
37
|
+
}
|
|
38
|
+
async initialize() {
|
|
39
|
+
console.log('Welcome to Cloving REPL. Type "exit" to quit.');
|
|
40
|
+
this.rl.prompt();
|
|
41
|
+
this.setupEventListeners();
|
|
42
|
+
await this.loadContextFiles();
|
|
43
|
+
}
|
|
44
|
+
setupEventListeners() {
|
|
45
|
+
this.rl.on('line', this.handleLine.bind(this));
|
|
46
|
+
this.rl.on('close', this.handleClose.bind(this));
|
|
47
|
+
process.stdin.on('keypress', this.handleKeypress.bind(this));
|
|
48
|
+
}
|
|
49
|
+
async loadContextFiles() {
|
|
50
|
+
let files = this.options.files || '.';
|
|
51
|
+
let expandedFiles = [];
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
const filePath = path_1.default.resolve(file);
|
|
54
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
|
|
55
|
+
const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
|
|
56
|
+
expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
files = expandedFiles;
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
const filePath = path_1.default.resolve(file);
|
|
65
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
66
|
+
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
67
|
+
this.contextFiles[file] = content;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async handleLine(line) {
|
|
72
|
+
if (this.isMultilineMode) {
|
|
73
|
+
if (line.trim() === '```') {
|
|
74
|
+
this.isMultilineMode = false;
|
|
75
|
+
line = this.multilineInput;
|
|
76
|
+
this.multilineInput = '';
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.multilineInput += line + '\n';
|
|
80
|
+
this.rl.prompt();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (line.trim() === '```') {
|
|
85
|
+
this.isMultilineMode = true;
|
|
86
|
+
this.multilineInput = '';
|
|
87
|
+
console.log('Entering multiline mode. Type ``` on a new line to end.');
|
|
88
|
+
this.rl.prompt();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const trimmedLine = line.trim().toLowerCase();
|
|
92
|
+
if (trimmedLine === '') {
|
|
93
|
+
this.rl.prompt();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.updateCommandHistory(trimmedLine);
|
|
97
|
+
if (trimmedLine === 'exit') {
|
|
98
|
+
this.rl.close();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await this.handleCommand(trimmedLine);
|
|
102
|
+
}
|
|
103
|
+
updateCommandHistory(command) {
|
|
104
|
+
if (this.commandHistory[0] !== command) {
|
|
105
|
+
this.commandHistory.unshift(command);
|
|
106
|
+
if (this.commandHistory.length > 1000) {
|
|
107
|
+
this.commandHistory.pop();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.historyIndex = -1;
|
|
111
|
+
}
|
|
112
|
+
async handleCommand(command) {
|
|
113
|
+
switch (command) {
|
|
114
|
+
case 'copy':
|
|
115
|
+
await this.handleCopy();
|
|
116
|
+
break;
|
|
117
|
+
case 'save':
|
|
118
|
+
await this.handleSave();
|
|
119
|
+
break;
|
|
120
|
+
case 'commit':
|
|
121
|
+
await this.handleCommit();
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
if (this.isGitCommand(command)) {
|
|
125
|
+
this.executeGitCommand(command);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
await this.processUserInput(command);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async handleCopy() {
|
|
133
|
+
const lastResponse = this.chatHistory.filter(msg => msg.role === 'assistant').pop();
|
|
134
|
+
if (lastResponse) {
|
|
135
|
+
copy_paste_1.default.copy(lastResponse.content, () => {
|
|
136
|
+
console.info('Last response copied to clipboard.');
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.error('No response to copy.');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async handleSave() {
|
|
144
|
+
const lastResponse = this.chatHistory.filter(msg => msg.role === 'assistant').pop();
|
|
145
|
+
if (lastResponse) {
|
|
146
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(lastResponse.content);
|
|
147
|
+
if (files.length > 0) {
|
|
148
|
+
await (0, string_utils_1.saveGeneratedFiles)(files, fileContents);
|
|
149
|
+
console.info('Files have been saved.');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.info('No files found to save in the last response.');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
console.error('No response to save files from.');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async handleCommit() {
|
|
160
|
+
const diff = (0, child_process_1.execSync)('git diff HEAD').toString().trim();
|
|
161
|
+
if (!diff) {
|
|
162
|
+
console.error('No changes to commit.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.prompt = (0, git_utils_1.generateCommitMessagePrompt)(diff);
|
|
166
|
+
this.gpt.stream = false;
|
|
167
|
+
const rawCommitMessage = await this.gpt.generateText({ prompt: this.prompt });
|
|
168
|
+
this.gpt.stream = true;
|
|
169
|
+
const commitMessage = (0, string_utils_1.extractMarkdown)(rawCommitMessage);
|
|
170
|
+
const tempCommitFilePath = path_1.default.join('.git', 'SUGGESTED_COMMIT_EDITMSG');
|
|
171
|
+
fs_1.default.writeFileSync(tempCommitFilePath, commitMessage);
|
|
172
|
+
try {
|
|
173
|
+
(0, child_process_1.execFileSync)('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], { stdio: 'inherit' });
|
|
174
|
+
}
|
|
175
|
+
catch (commitError) {
|
|
176
|
+
console.error('Commit was canceled or failed.');
|
|
177
|
+
}
|
|
178
|
+
fs_1.default.unlink(tempCommitFilePath, (err) => {
|
|
179
|
+
if (err)
|
|
180
|
+
throw err;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
isGitCommand(command) {
|
|
184
|
+
return command.split(' ').length <= 3 && command.startsWith('git ');
|
|
185
|
+
}
|
|
186
|
+
executeGitCommand(command) {
|
|
187
|
+
try {
|
|
188
|
+
(0, child_process_1.execSync)(command, { stdio: 'inherit' });
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error('Error running command:', error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async processUserInput(input) {
|
|
195
|
+
try {
|
|
196
|
+
this.chatHistory.push({ role: 'user', content: input });
|
|
197
|
+
this.prompt = this.generatePrompt(input);
|
|
198
|
+
const responseStream = await this.gpt.streamText({ prompt: this.prompt });
|
|
199
|
+
let accumulatedContent = '';
|
|
200
|
+
this.chunkManager.on('content', (buffer) => {
|
|
201
|
+
let convertedStream = this.gpt.convertStream(buffer);
|
|
202
|
+
while (convertedStream !== null) {
|
|
203
|
+
const { output, lastChar } = convertedStream;
|
|
204
|
+
process.stdout.write(output);
|
|
205
|
+
accumulatedContent += output;
|
|
206
|
+
this.chunkManager.clearBuffer(lastChar);
|
|
207
|
+
buffer = buffer.slice(lastChar);
|
|
208
|
+
convertedStream = this.gpt.convertStream(buffer);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
responseStream.data.on('data', (chunk) => {
|
|
212
|
+
const chunkString = chunk.toString();
|
|
213
|
+
this.chunkManager.addChunk(chunkString);
|
|
214
|
+
});
|
|
215
|
+
responseStream.data.on('end', () => {
|
|
216
|
+
this.chatHistory.push({ role: 'assistant', content: accumulatedContent.trim() });
|
|
217
|
+
this.rl.prompt();
|
|
218
|
+
});
|
|
219
|
+
responseStream.data.on('error', (error) => {
|
|
220
|
+
console.error('Error streaming response:', error);
|
|
221
|
+
this.rl.prompt();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
const error = err;
|
|
226
|
+
let errorMessage = error.message || 'An error occurred.';
|
|
227
|
+
const errorNumber = error.response?.status || 'unknown';
|
|
228
|
+
// switch case
|
|
229
|
+
switch (errorNumber) {
|
|
230
|
+
case 400:
|
|
231
|
+
errorMessage = "Invalid model or prompt size too large. Try specifying fewer files.";
|
|
232
|
+
break;
|
|
233
|
+
case 403:
|
|
234
|
+
errorMessage = "Inactive subscription or usage limit reached";
|
|
235
|
+
break;
|
|
236
|
+
case 429:
|
|
237
|
+
errorMessage = "Rate limit error";
|
|
238
|
+
break;
|
|
239
|
+
case 500:
|
|
240
|
+
errorMessage = "Internal server error";
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
// get token estimate for prompt
|
|
244
|
+
const promptTokens = Math.ceil(this.prompt.length / 4).toLocaleString();
|
|
245
|
+
console.error(`Error processing a ${promptTokens} token prompt:`, errorMessage, `(${errorNumber})`);
|
|
246
|
+
this.rl.prompt();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
generatePrompt(input) {
|
|
250
|
+
const contextFileContents = Object.keys(this.contextFiles)
|
|
251
|
+
.map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`)
|
|
252
|
+
.join('\n');
|
|
253
|
+
return `${contextFileContents}
|
|
254
|
+
|
|
255
|
+
### Task
|
|
256
|
+
|
|
257
|
+
${PREAMBLE}
|
|
258
|
+
|
|
259
|
+
${this.chatHistory.map(msg => `${msg.role.toUpperCase()}: ${msg.content}`).join('\n\n')}`;
|
|
260
|
+
}
|
|
261
|
+
handleClose() {
|
|
262
|
+
console.log('Goodbye!');
|
|
263
|
+
process.exit(0);
|
|
264
|
+
}
|
|
265
|
+
handleKeypress(_, key) {
|
|
266
|
+
if (key && (key.name === 'up' || key.name === 'down')) {
|
|
267
|
+
if (key.name === 'up' && this.historyIndex < this.commandHistory.length - 1) {
|
|
268
|
+
this.historyIndex++;
|
|
269
|
+
}
|
|
270
|
+
else if (key.name === 'down' && this.historyIndex > -1) {
|
|
271
|
+
this.historyIndex--;
|
|
272
|
+
}
|
|
273
|
+
if (this.historyIndex >= 0) {
|
|
274
|
+
this.rl.write(null, { ctrl: true, name: 'u' });
|
|
275
|
+
this.rl.write(this.commandHistory[this.historyIndex]);
|
|
276
|
+
}
|
|
277
|
+
else if (this.historyIndex === -1) {
|
|
278
|
+
this.rl.write(null, { ctrl: true, name: 'u' });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
exports.default = ChatManager;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const events_1 = require("events");
|
|
4
|
+
class ChunkManager extends events_1.EventEmitter {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
|
+
this.buffer = '';
|
|
8
|
+
}
|
|
9
|
+
addChunk(chunk) {
|
|
10
|
+
this.buffer += chunk;
|
|
11
|
+
this.emit('content', this.buffer);
|
|
12
|
+
}
|
|
13
|
+
clearBuffer(lastChar) {
|
|
14
|
+
this.buffer = this.buffer.substring(lastChar);
|
|
15
|
+
}
|
|
16
|
+
getBuffer() {
|
|
17
|
+
return this.buffer;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.default = ChunkManager;
|
|
@@ -0,0 +1,319 @@
|
|
|
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
|
+
class CodeManager {
|
|
15
|
+
constructor(gpt, options, allSrcFiles) {
|
|
16
|
+
this.gpt = gpt;
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.allSrcFiles = allSrcFiles;
|
|
19
|
+
this.contextFiles = {};
|
|
20
|
+
}
|
|
21
|
+
generateCodePrompt(prompt, previousCode) {
|
|
22
|
+
const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
|
|
23
|
+
const specialFiles = Object.keys(specialFileContents).map((file) => `### Contents of ${file}\n\n${JSON.stringify(specialFileContents[file], null, 2)}\n\n`).join('\n');
|
|
24
|
+
const contextFileContents = Object.keys(this.contextFiles).map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`).join('\n');
|
|
25
|
+
let promptText = `### Request
|
|
26
|
+
|
|
27
|
+
Generate code that does the following: ${prompt}
|
|
28
|
+
|
|
29
|
+
### Description of App
|
|
30
|
+
|
|
31
|
+
${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
|
|
32
|
+
|
|
33
|
+
${specialFiles}
|
|
34
|
+
|
|
35
|
+
${contextFileContents}
|
|
36
|
+
|
|
37
|
+
### Directory structure
|
|
38
|
+
|
|
39
|
+
${this.allSrcFiles.join('\n')}
|
|
40
|
+
|
|
41
|
+
`;
|
|
42
|
+
if (previousCode) {
|
|
43
|
+
promptText += `### Previously generated code
|
|
44
|
+
|
|
45
|
+
${previousCode}
|
|
46
|
+
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
promptText += `### Example of a well-structured response
|
|
50
|
+
|
|
51
|
+
Here are the files that I would like to generate:
|
|
52
|
+
|
|
53
|
+
1. **src/commands/generate/shell.ts**
|
|
54
|
+
|
|
55
|
+
\`\`\`typescript
|
|
56
|
+
import inquirer from 'inquirer'
|
|
57
|
+
import highlight from 'cli-highlight'
|
|
58
|
+
import { execSync } from 'child_process'
|
|
59
|
+
import ncp from 'copy-paste'
|
|
60
|
+
import { extractMarkdown } from '../../utils/string_utils'
|
|
61
|
+
import { getConfig } from '../../utils/config_utils'
|
|
62
|
+
import ClovingGPT from '../../cloving_gpt'
|
|
63
|
+
import type { ClovingGPTOptions } from '../../utils/types'
|
|
64
|
+
|
|
65
|
+
const generateShellPrompt = (prompt: string | undefined): string => {
|
|
66
|
+
const shell = execSync('echo $SHELL').toString().trim()
|
|
67
|
+
const os = execSync('echo $OSTYPE').toString().trim()
|
|
68
|
+
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.
|
|
69
|
+
|
|
70
|
+
Do not add any commentary or context to the message other than the commit message itself.
|
|
71
|
+
|
|
72
|
+
An example of the output for this should look like the following:
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
### Request
|
|
76
|
+
|
|
77
|
+
Generate code that does the following: ${prompt}
|
|
78
|
+
|
|
79
|
+
Do not use any data from the example response structure, only use the structure. 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.`;
|
|
80
|
+
return promptText;
|
|
81
|
+
}
|
|
82
|
+
displayGeneratedCode(rawCodeCommand) {
|
|
83
|
+
(0, string_utils_1.parseMarkdownInstructions)(rawCodeCommand).map(code => {
|
|
84
|
+
if (code.trim().startsWith('```')) {
|
|
85
|
+
const lines = code.split('\n');
|
|
86
|
+
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
87
|
+
console.log(lines[0]);
|
|
88
|
+
try {
|
|
89
|
+
console.log((0, cli_highlight_1.default)(lines.slice(1, -1).join('\n'), { language }));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// don't highlight if it fails
|
|
93
|
+
console.log(lines.slice(1, -1).join('\n'));
|
|
94
|
+
}
|
|
95
|
+
console.log(lines.slice(-1)[0]);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log((0, cli_highlight_1.default)(code, { language: 'markdown' }));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async updateContextFiles(files, fileContents) {
|
|
103
|
+
for (const file of files) {
|
|
104
|
+
if (fileContents[file]) {
|
|
105
|
+
this.contextFiles[file] = fileContents[file];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async handleUserAction(rawCodeCommand, prompt) {
|
|
110
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
111
|
+
if (this.options.save) {
|
|
112
|
+
await (0, string_utils_1.saveGeneratedFiles)(files, fileContents);
|
|
113
|
+
await this.updateContextFiles(files, fileContents);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const action = await (0, prompts_1.select)({
|
|
117
|
+
message: 'What would you like to do?',
|
|
118
|
+
choices: [
|
|
119
|
+
{ name: 'Revise', value: 'revise' },
|
|
120
|
+
{ name: 'Explain', value: 'explain' },
|
|
121
|
+
{ name: 'Save a Source Code File', value: 'save' },
|
|
122
|
+
{ name: 'Save All Source Code Files', value: 'saveAll' },
|
|
123
|
+
{ name: 'Copy Source Code to Clipboard', value: 'copySource' },
|
|
124
|
+
{ name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
|
|
125
|
+
{ name: 'Done', value: 'done' },
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
switch (action) {
|
|
129
|
+
case 'revise':
|
|
130
|
+
const newPrompt = await (0, prompts_1.input)({
|
|
131
|
+
message: 'How would you like to modify the output:',
|
|
132
|
+
});
|
|
133
|
+
let includeMoreFiles = true;
|
|
134
|
+
while (includeMoreFiles) {
|
|
135
|
+
const contextFile = await (0, prompts_1.input)({
|
|
136
|
+
message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
137
|
+
});
|
|
138
|
+
if (contextFile) {
|
|
139
|
+
this.contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, this.contextFiles, this.options);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
includeMoreFiles = false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const newRawCodeCommand = await this.generateCode(newPrompt, rawCodeCommand);
|
|
146
|
+
this.displayGeneratedCode(newRawCodeCommand);
|
|
147
|
+
await this.handleUserAction(newRawCodeCommand, newPrompt);
|
|
148
|
+
break;
|
|
149
|
+
case 'explain':
|
|
150
|
+
const explainPrompt = this.generateExplainCodePrompt(rawCodeCommand);
|
|
151
|
+
const explainCodeCommand = await this.gpt.generateText({ prompt: explainPrompt });
|
|
152
|
+
console.log((0, cli_highlight_1.default)(explainCodeCommand, { language: 'markdown' }));
|
|
153
|
+
break;
|
|
154
|
+
case 'copySource':
|
|
155
|
+
let copyAnother = true;
|
|
156
|
+
while (copyAnother) {
|
|
157
|
+
const fileToCopy = await (0, prompts_1.select)({
|
|
158
|
+
message: 'Which file do you want to copy to the clipboard?',
|
|
159
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
160
|
+
});
|
|
161
|
+
if (fileContents[fileToCopy]) {
|
|
162
|
+
copy_paste_1.default.copy(fileContents[fileToCopy], () => {
|
|
163
|
+
console.log(`${fileToCopy} copied to clipboard`);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.log('File content not found.');
|
|
168
|
+
}
|
|
169
|
+
const copyMore = await (0, prompts_1.confirm)({
|
|
170
|
+
message: 'Do you want to copy another file?',
|
|
171
|
+
default: true,
|
|
172
|
+
});
|
|
173
|
+
copyAnother = copyMore;
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
case 'copyAll':
|
|
177
|
+
copy_paste_1.default.copy(rawCodeCommand, () => {
|
|
178
|
+
console.log('Entire response copied to clipboard');
|
|
179
|
+
});
|
|
180
|
+
break;
|
|
181
|
+
case 'save':
|
|
182
|
+
let saveAnother = true;
|
|
183
|
+
while (saveAnother) {
|
|
184
|
+
const fileToSave = await (0, prompts_1.select)({
|
|
185
|
+
message: 'Which file do you want to save?',
|
|
186
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
187
|
+
});
|
|
188
|
+
if (fileContents[fileToSave]) {
|
|
189
|
+
const filePath = path_1.default.resolve(fileToSave);
|
|
190
|
+
await fs_1.default.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
191
|
+
await fs_1.default.promises.writeFile(filePath, fileContents[fileToSave]);
|
|
192
|
+
console.log(`${fileToSave} has been saved.`);
|
|
193
|
+
await this.updateContextFiles([fileToSave], fileContents);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
console.log('File content not found.');
|
|
197
|
+
}
|
|
198
|
+
const saveMore = await (0, prompts_1.confirm)({
|
|
199
|
+
message: 'Do you want to save another file?',
|
|
200
|
+
default: true,
|
|
201
|
+
});
|
|
202
|
+
saveAnother = saveMore;
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
case 'saveAll':
|
|
206
|
+
await (0, string_utils_1.saveGeneratedFiles)(files, fileContents);
|
|
207
|
+
await this.updateContextFiles(files, fileContents);
|
|
208
|
+
console.log('All files have been saved.');
|
|
209
|
+
break;
|
|
210
|
+
case 'done':
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
generateExplainCodePrompt(prompt) {
|
|
215
|
+
return `${prompt}
|
|
216
|
+
|
|
217
|
+
## Task
|
|
218
|
+
|
|
219
|
+
Please briefly explain how the code works in this.`;
|
|
220
|
+
}
|
|
221
|
+
async generateCode(prompt, previousCode) {
|
|
222
|
+
const codePrompt = this.generateCodePrompt(prompt, previousCode);
|
|
223
|
+
return await this.gpt.generateText({ prompt: codePrompt });
|
|
224
|
+
}
|
|
225
|
+
async processCode(prompt) {
|
|
226
|
+
try {
|
|
227
|
+
if (this.options.files) {
|
|
228
|
+
let expandedFiles = [];
|
|
229
|
+
for (const file of this.options.files) {
|
|
230
|
+
const filePath = path_1.default.resolve(file);
|
|
231
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
|
|
232
|
+
const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
|
|
233
|
+
expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
this.options.files = expandedFiles;
|
|
240
|
+
for (const file of this.options.files) {
|
|
241
|
+
const filePath = path_1.default.resolve(file);
|
|
242
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
243
|
+
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
244
|
+
this.contextFiles[file] = content;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
let includeMoreFiles = true;
|
|
250
|
+
while (includeMoreFiles) {
|
|
251
|
+
const contextFile = await (0, prompts_1.input)({
|
|
252
|
+
message: `Enter the relative path of a file or directory you would like to include as context (or press enter to continue):`,
|
|
253
|
+
});
|
|
254
|
+
if (contextFile) {
|
|
255
|
+
this.contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, this.contextFiles, this.options);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
includeMoreFiles = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (!prompt) {
|
|
263
|
+
const userPrompt = await (0, prompts_1.input)({
|
|
264
|
+
message: 'What would you like the code to do:',
|
|
265
|
+
});
|
|
266
|
+
prompt = userPrompt;
|
|
267
|
+
}
|
|
268
|
+
let rawCodeCommand = await this.generateCode(prompt);
|
|
269
|
+
this.displayGeneratedCode(rawCodeCommand);
|
|
270
|
+
if (this.options.save) {
|
|
271
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
272
|
+
await (0, string_utils_1.saveGeneratedFiles)(files, fileContents);
|
|
273
|
+
await this.updateContextFiles(files, fileContents);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
await this.handleUserAction(rawCodeCommand, prompt);
|
|
277
|
+
}
|
|
278
|
+
if (this.options.interactive) {
|
|
279
|
+
let continueInteractive = true;
|
|
280
|
+
while (continueInteractive) {
|
|
281
|
+
const newPrompt = await (0, prompts_1.input)({
|
|
282
|
+
message: 'Revise the code (or press enter to finish):',
|
|
283
|
+
});
|
|
284
|
+
if (newPrompt.trim() === '') {
|
|
285
|
+
continueInteractive = false;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
let includeMoreFiles = true;
|
|
289
|
+
while (includeMoreFiles) {
|
|
290
|
+
const contextFile = await (0, prompts_1.input)({
|
|
291
|
+
message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
292
|
+
});
|
|
293
|
+
if (contextFile) {
|
|
294
|
+
this.contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, this.contextFiles, this.options);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
includeMoreFiles = false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
rawCodeCommand = await this.generateCode(newPrompt, rawCodeCommand);
|
|
301
|
+
this.displayGeneratedCode(rawCodeCommand);
|
|
302
|
+
if (this.options.save || this.options.interactive) {
|
|
303
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
304
|
+
await (0, string_utils_1.saveGeneratedFiles)(files, fileContents);
|
|
305
|
+
await this.updateContextFiles(files, fileContents);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
await this.handleUserAction(rawCodeCommand, newPrompt);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
console.error('Could not generate code', error);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
exports.default = CodeManager;
|