cloving 0.1.18 → 0.1.20

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.
@@ -13,6 +13,9 @@ const mistral_1 = require("./adapters/mistral");
13
13
  const ollama_1 = require("./adapters/ollama");
14
14
  const gemini_1 = require("./adapters/gemini");
15
15
  const config_utils_1 = require("../utils/config_utils");
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const os_1 = __importDefault(require("os"));
18
+ const path_1 = __importDefault(require("path"));
16
19
  class ClovingGPT {
17
20
  constructor(options) {
18
21
  const { model: partialModel } = options;
@@ -52,51 +55,53 @@ class ClovingGPT {
52
55
  }
53
56
  async reviewPrompt(prompt, endpoint) {
54
57
  if (this.silent)
55
- return;
58
+ return prompt;
56
59
  const tokenCount = Math.ceil(prompt.length / 4).toLocaleString();
57
60
  const reviewPrompt = await (0, prompts_1.confirm)({
58
- message: `Do you want to review the ~${tokenCount} token prompt before sending it to ${endpoint}?`,
61
+ message: `Do you want to review/edit the ~${tokenCount} token prompt before sending it to ${endpoint}?`,
59
62
  default: true
60
63
  });
61
64
  if (reviewPrompt) {
62
- const less = (0, child_process_1.spawn)('less', ['-R'], { stdio: ['pipe', process_1.default.stdout, process_1.default.stderr] });
63
- less.stdin.write(prompt);
64
- less.stdin.end();
65
- await new Promise((resolve, reject) => {
66
- less.on('close', async (code) => {
65
+ const tempFile = path_1.default.join(os_1.default.tmpdir(), `cloving_prompt_${Date.now()}.txt`);
66
+ fs_1.default.writeFileSync(tempFile, prompt);
67
+ const editor = process_1.default.env.EDITOR || 'nano';
68
+ const editProcess = (0, child_process_1.spawn)(editor, [tempFile], { stdio: 'inherit' });
69
+ return new Promise((resolve, reject) => {
70
+ editProcess.on('close', async (code) => {
67
71
  if (code !== 0) {
68
- console.error('less command exited with an error');
69
- reject(new Error('Less command failed'));
72
+ console.error(`${editor} exited with code ${code}`);
73
+ fs_1.default.unlinkSync(tempFile);
74
+ reject(new Error(`Editor exited with non-zero code`));
70
75
  return;
71
76
  }
72
- const confirmContinue = await (0, prompts_1.confirm)({
73
- message: 'Do you want to continue?',
74
- default: true
75
- });
76
- if (!confirmContinue) {
77
- console.error('Operation cancelled');
78
- process_1.default.exit(1);
77
+ const editedPrompt = fs_1.default.readFileSync(tempFile, 'utf-8').trim();
78
+ fs_1.default.unlinkSync(tempFile);
79
+ if (editedPrompt === '') {
80
+ console.log('Prompt is empty.');
81
+ resolve(null);
82
+ return;
79
83
  }
80
- resolve();
84
+ resolve(editedPrompt);
81
85
  });
82
- less.stdin.on('error', (err) => {
83
- if (isNodeError(err) && err.code === 'EPIPE') {
84
- resolve();
85
- }
86
- else {
87
- console.error('Pipeline error:', err);
88
- reject(err);
89
- }
86
+ editProcess.on('error', (err) => {
87
+ console.error('Error launching editor:', err);
88
+ fs_1.default.unlinkSync(tempFile);
89
+ reject(err);
90
90
  });
91
91
  });
92
92
  }
93
+ return prompt;
93
94
  }
94
95
  async generateText(request) {
95
96
  request.temperature || (request.temperature = this.temperature);
96
97
  const endpoint = this.adapter.getEndpoint();
97
98
  const payload = this.adapter.getPayload(request);
98
99
  const headers = this.adapter.getHeaders(this.apiKey);
99
- await this.reviewPrompt(request.prompt, endpoint);
100
+ const reviewedPrompt = await this.reviewPrompt(request.prompt, endpoint);
101
+ if (reviewedPrompt === null) {
102
+ throw new Error('Operation cancelled by user');
103
+ }
104
+ request.prompt = reviewedPrompt;
100
105
  try {
101
106
  const response = await axios_1.default.post(endpoint, payload, { headers });
102
107
  return this.adapter.extractResponse(response.data);
@@ -111,7 +116,4 @@ class ClovingGPT {
111
116
  }
112
117
  }
113
118
  }
114
- function isNodeError(error) {
115
- return error instanceof Error && 'code' in error;
116
- }
117
119
  exports.default = ClovingGPT;
@@ -232,8 +232,20 @@ const code = async (options) => {
232
232
  const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, false);
233
233
  let contextFiles = {};
234
234
  try {
235
- if (allSrcFiles) {
236
- for (const file of allSrcFiles) {
235
+ if (files) {
236
+ let expandedFiles = [];
237
+ for (const file of files) {
238
+ const filePath = path_1.default.resolve(file);
239
+ if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
240
+ const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
241
+ expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
242
+ }
243
+ else {
244
+ expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
245
+ }
246
+ }
247
+ files = expandedFiles;
248
+ for (const file of files) {
237
249
  const filePath = path_1.default.resolve(file);
238
250
  if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
239
251
  const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
@@ -6,69 +6,111 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const cli_highlight_1 = __importDefault(require("cli-highlight"));
7
7
  const prompts_1 = require("@inquirer/prompts");
8
8
  const copy_paste_1 = __importDefault(require("copy-paste"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
9
11
  const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
10
12
  const git_utils_1 = require("../../utils/git_utils");
11
- const config_utils_1 = require("../../utils/config_utils");
12
13
  const string_utils_1 = require("../../utils/string_utils");
14
+ const command_utils_1 = require("../../utils/command_utils");
15
+ const config_utils_1 = require("../../utils/config_utils");
13
16
  const review = async (options) => {
17
+ let { prompt, files } = options;
14
18
  options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
15
19
  const gpt = new cloving_gpt_1.default(options);
16
- try {
17
- // Define the prompt for analysis
18
- const gitDiff = await (0, git_utils_1.getGitDiff)();
19
- const promptText = `==== begin diff =====
20
- ${gitDiff}
21
- ==== end diff =====
20
+ let promptText = `### Prompt
21
+
22
+ ${prompt || 'Please provide a code review.'}
22
23
 
23
24
  ### Example of a well-structured response
24
25
 
25
- # Code Review: Enhanced Code Generation and File Handling
26
+ # Code Review: Enhanced Code Generation and File Handling
26
27
 
27
- ## Changes Overview
28
+ ## Changes Overview
28
29
 
29
- These changes primarily focus on improving the code generation process and adding more robust file handling capabilities. The main modifications include:
30
+ These changes primarily focus on improving the code generation process and adding more robust file handling capabilities. The main modifications include:
30
31
 
31
- 1. Enhancing the code generation prompt with a structured example response.
32
- 2. Implementing a new function to extract file names and contents from the generated code.
33
- 3. Adding a new option to save generated code directly to files.
34
- 4. Improving the copy-to-clipboard functionality to allow copying individual files.
35
- 5. Removing an unused import in the review.ts file.
32
+ 1. Enhancing the code generation prompt with a structured example response.
33
+ 2. Implementing a new function to extract file names and contents from the generated code.
34
+ 3. Adding a new option to save generated code directly to files.
35
+ 4. Improving the copy-to-clipboard functionality to allow copying individual files.
36
+ 5. Removing an unused import in the review.ts file.
36
37
 
37
- ## Reason for Changes
38
+ ## Reason for Changes
38
39
 
39
- The changes aim to enhance the usability and flexibility of the code generation tool by providing clearer prompts, better file management, and more control over the generated code.
40
+ The changes aim to enhance the usability and flexibility of the code generation tool by providing clearer prompts, better file management, and more control over the generated code.
40
41
 
41
- ## Detailed Description
42
+ ## Detailed Description
42
43
 
43
- 1. **Enhanced Code Generation Prompt** - The \`generateCodePrompt\` function now includes a structured example of a well-formatted response. This helps guide the AI to produce more consistent and usable output, making it easier to parse and use the generated code.
44
- 2. **File Extraction Function** - A new function \`extractFilesAndContent\` has been added to parse the raw code command and extract individual file names and their contents. This enables more granular control over the generated code, allowing for file-specific operations.
45
- 3. **Save to File Option** - A new 'Save Source Code to Files' option has been added to the user action menu. This feature allows users to save generated code directly to files in the appropriate directory structure.
46
- 4. **Improved Copy to Clipboard** - The 'Copy Source Code to Clipboard' option has been enhanced to allow users to choose specific files to copy, rather than copying all generated code at once.
47
- 5. **Removed Unused Import** - The unused import of \`execFileSync\` from 'child_process' has been removed from the review.ts file, improving code cleanliness.
44
+ 1. **Enhanced Code Generation Prompt** - The \`generateCodePrompt\` function now includes a structured example of a well-formatted response. This helps guide the AI to produce more consistent and usable output, making it easier to parse and use the generated code.
45
+ 2. **File Extraction Function** - A new function \`extractFilesAndContent\` has been added to parse the raw code command and extract individual file names and their contents. This enables more granular control over the generated code, allowing for file-specific operations.
46
+ 3. **Save to File Option** - A new 'Save Source Code to Files' option has been added to the user action menu. This feature allows users to save generated code directly to files in the appropriate directory structure.
47
+ 4. **Improved Copy to Clipboard** - The 'Copy Source Code to Clipboard' option has been enhanced to allow users to choose specific files to copy, rather than copying all generated code at once.
48
+ 5. **Removed Unused Import** - The unused import of \`execFileSync\` from 'child_process' has been removed from the review.ts file, improving code cleanliness.
48
49
 
49
- ## Potential Bugs and Recommended Fixes
50
+ ## Potential Bugs and Recommended Fixes
50
51
 
51
- ### 1. Possible undefined access in \`extractFilesAndContent\`
52
+ ### 1. Possible undefined access in \`extractFilesAndContent\`
52
53
 
53
- \`\`\`typescript
54
- const contentMatch = regex.exec(rawCodeCommand)
55
- if (contentMatch) {
56
- files.push(fileName)
57
- let content = contentMatch[1]
58
- // ...
59
- }
60
- \`\`\`
54
+ \`\`\`typescript
55
+ const contentMatch = regex.exec(rawCodeCommand)
56
+ if (contentMatch) {
57
+ files.push(fileName)
58
+ let content = contentMatch[1]
59
+ // ...
60
+ }
61
+ \`\`\`
61
62
 
62
- There's a potential for \`contentMatch[1]\` to be undefined if the regex doesn't capture any groups. To fix this, add a null check:
63
+ There's a potential for \`contentMatch[1]\` to be undefined if the regex doesn't capture any groups. To fix this, add a null check:
63
64
 
64
- \`\`\`typescript
65
- const contentMatch = regex.exec(rawCodeCommand)
66
- if (contentMatch && contentMatch[1]) {
67
- files.push(fileName)
68
- let content = contentMatch[1]
69
- // ...
70
- }
71
- \`\`\`
65
+ \`\`\`typescript
66
+ const contentMatch = regex.exec(rawCodeCommand)
67
+ if (contentMatch && contentMatch[1]) {
68
+ files.push(fileName)
69
+ let content = contentMatch[1]
70
+ // ...
71
+ }
72
+ \`\`\`
73
+ `;
74
+ let contextFiles = {};
75
+ try {
76
+ // Define the prompt for analysis
77
+ if (files) {
78
+ let expandedFiles = [];
79
+ for (const file of files) {
80
+ const filePath = path_1.default.resolve(file);
81
+ if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
82
+ const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
83
+ expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
84
+ }
85
+ else {
86
+ expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
87
+ }
88
+ }
89
+ files = expandedFiles;
90
+ for (const file of files) {
91
+ const filePath = path_1.default.resolve(file);
92
+ if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
93
+ const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
94
+ contextFiles[file] = content;
95
+ }
96
+ }
97
+ const contextFileContents = Object.keys(contextFiles).map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`).join('\n');
98
+ promptText += `
99
+ ### Description of App
100
+
101
+ ${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
102
+
103
+ ${contextFileContents}
104
+
105
+ `;
106
+ }
107
+ else {
108
+ const gitDiff = await (0, git_utils_1.getGitDiff)();
109
+ promptText = `==== begin diff =====
110
+ ${gitDiff}
111
+ ==== end diff =====`;
112
+ }
113
+ promptText += `
72
114
 
73
115
  ### Request
74
116
 
@@ -76,6 +118,13 @@ Do not use any data from the example response structure, only use the structure.
76
118
  I would like you to explain why these change are being made and document a description of these changes.
77
119
  Also list any bugs in the new code as well as recommended fixes for those bugs with code examples.
78
120
  Format the output of this code review in Markdown format.`;
121
+ if (prompt) {
122
+ promptText += `
123
+
124
+ ### Additional Context
125
+
126
+ ${prompt}`;
127
+ }
79
128
  // get the analysis
80
129
  const analysis = await gpt.generateText({ prompt: promptText });
81
130
  // Print the analysis to the console
package/dist/index.js CHANGED
@@ -106,6 +106,8 @@ generate
106
106
  .alias('r')
107
107
  .description('Review the code for commited changes that differ from the main/master branch')
108
108
  .option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
109
+ .option('-f, --files <filenames...>', 'Specify filenames for files you want to review')
110
+ .option('-p, --prompt <prompt>', 'Add a prompt to the review')
109
111
  .option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5-sonnet-20240620)')
110
112
  .option('-t, --temperature <temperature>', 'Temperature for the model (default 0.2)')
111
113
  .action(review_1.default);
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "packageManager": "yarn@1.22.22",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.addFileOrDirectoryToContext = exports.readFileContent = exports.fetchModels = exports.runCommand = exports.generateFileList = exports.checkForSpecialFiles = exports.collectSpecialFileContents = exports.SPECIAL_FILES = void 0;
6
+ exports.addFileOrDirectoryToContext = exports.readFileContent = exports.fetchModels = exports.runCommand = exports.generateFileList = exports.getAllFilesInDirectory = exports.checkForSpecialFiles = exports.collectSpecialFileContents = exports.SPECIAL_FILES = void 0;
7
7
  const child_process_1 = require("child_process");
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
@@ -33,6 +33,18 @@ const collectSpecialFileContents = () => {
33
33
  exports.collectSpecialFileContents = collectSpecialFileContents;
34
34
  const checkForSpecialFiles = () => exports.SPECIAL_FILES.some(file => fs_1.default.existsSync(file));
35
35
  exports.checkForSpecialFiles = checkForSpecialFiles;
36
+ const getAllFilesInDirectory = async (dir) => {
37
+ const subdirs = await fs_1.default.promises.readdir(dir);
38
+ const files = await Promise.all(subdirs.map(async (subdir) => {
39
+ const res = path_1.default.resolve(dir, subdir);
40
+ if (subdir === 'node_modules' || subdir === '.git') {
41
+ return [];
42
+ }
43
+ return (await fs_1.default.promises.stat(res)).isDirectory() ? (0, exports.getAllFilesInDirectory)(res) : res;
44
+ }));
45
+ return files.flat();
46
+ };
47
+ exports.getAllFilesInDirectory = getAllFilesInDirectory;
36
48
  const generateFileList = async () => {
37
49
  try {
38
50
  const lsOutput = await (0, exports.runCommand)('ls', []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "packageManager": "yarn@1.22.22",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",