cloving 0.1.0 → 0.1.2

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 CHANGED
@@ -20,7 +20,7 @@ Cloving is an innovative tool designed to seamlessly integrate AI into various a
20
20
 
21
21
  1. Clone this repository:
22
22
  ```bash
23
- git clone https://github.com/clovingai/cloving-cli.git
23
+ git clone https://github.com/cloving-ai/cloving-cli.git
24
24
  ```
25
25
 
26
26
  2. Navigate to the project directory:
@@ -28,15 +28,15 @@ Cloving is an innovative tool designed to seamlessly integrate AI into various a
28
28
  cd cloving-cli
29
29
  ```
30
30
 
31
- 3. Install dependencies:
31
+ 3. Install dependencies and link the package:
32
32
  ```bash
33
33
  yarn install
34
+ yarn link
34
35
  ```
35
36
 
36
- 4. Set up your AI chat model environment variables:
37
+ 4. Configure Cloving with your API key and model:
37
38
  ```bash
38
- export CLOVING_MODEL=your_preferred_model
39
- export CLOVING_API_KEY=your_api_key
39
+ cloving config
40
40
  ```
41
41
 
42
42
  ## Usage
@@ -14,13 +14,14 @@ const ollama_1 = require("./adapters/ollama");
14
14
  const command_utils_1 = require("../utils/command_utils");
15
15
  class ClovingGPT {
16
16
  constructor(options = { silent: false }) {
17
- const config = (0, command_utils_1.getConfig)();
17
+ const config = (0, command_utils_1.getConfig)(options);
18
18
  if (!config || !config.primaryModel || !config.models) {
19
- throw new Error('No cloving configuration found. Please run `cloving config`');
19
+ console.log('No cloving configuration found. Please run: cloving config');
20
+ process_1.default.exit(1);
20
21
  }
21
22
  const clovingModel = config.primaryModel;
22
23
  this.apiKey = config.models[config?.primaryModel || ''].trim();
23
- this.silent = options.silent;
24
+ this.silent = options.silent || false;
24
25
  const parts = clovingModel.split(':');
25
26
  const model = parts.slice(1).join(':');
26
27
  switch (parts[0]) {
@@ -57,7 +58,7 @@ class ClovingGPT {
57
58
  less.stdin.end();
58
59
  less.on('close', (code) => {
59
60
  if (code !== 0) {
60
- console.error('less command exited with an error.');
61
+ console.error('less command exited with an error');
61
62
  resolve(false);
62
63
  return;
63
64
  }
@@ -68,7 +69,13 @@ class ClovingGPT {
68
69
  rlConfirm.question('Do you still want to continue? [Yn]: ', (confirmAnswer) => {
69
70
  rlConfirm.close();
70
71
  confirmAnswer = confirmAnswer.trim().toLowerCase();
71
- resolve(confirmAnswer === 'y' || confirmAnswer === '');
72
+ if (confirmAnswer !== 'y' && confirmAnswer !== '') {
73
+ console.error('User cancelled the operation');
74
+ process_1.default.exit(1);
75
+ }
76
+ else {
77
+ resolve(true);
78
+ }
72
79
  });
73
80
  });
74
81
  less.stdin.on('error', (err) => {
@@ -11,7 +11,7 @@ const string_utils_1 = require("../utils/string_utils");
11
11
  const command_utils_1 = require("../utils/command_utils");
12
12
  const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
13
13
  const generateAndCommitMessage = async (options) => {
14
- options.silent = (0, command_utils_1.getConfig)().silent || false;
14
+ options.silent = (0, command_utils_1.getConfig)(options).silent || false;
15
15
  const gpt = new cloving_gpt_1.default(options);
16
16
  try {
17
17
  // Generate the prompt for commit message
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.config = void 0;
4
4
  const command_utils_1 = require("../utils/command_utils");
5
5
  const config = async () => {
6
- let currentConfig = (0, command_utils_1.getConfig)();
6
+ let currentConfig = (0, command_utils_1.getConfig)({});
7
7
  // Ensure models is initialized properly
8
8
  if (!currentConfig.models) {
9
9
  currentConfig.models = {};
@@ -18,8 +18,7 @@ const config = async () => {
18
18
  console.log('Available models:');
19
19
  models.forEach((model, index) => console.log(`${index + 1}. ${model}`));
20
20
  const modelIndex = parseInt(await (0, command_utils_1.promptUser)('Select a model by number: '), 10) - 1;
21
- if (modelIndex < 0 || modelIndex >= models.length) {
22
- console.error('Invalid selection.');
21
+ if (isNaN(modelIndex) || modelIndex < 0 || modelIndex >= models.length) {
23
22
  throw new Error('Invalid selection');
24
23
  }
25
24
  const selectedModel = models[modelIndex];
@@ -14,7 +14,7 @@ const string_utils_2 = require("../utils/string_utils");
14
14
  const command_utils_1 = require("../utils/command_utils");
15
15
  // Main function for the describe command
16
16
  const init = async (options) => {
17
- options.silent = (0, command_utils_1.getConfig)().silent || false;
17
+ options.silent = (0, command_utils_1.getConfig)(options).silent || false;
18
18
  const gpt = new cloving_gpt_1.default(options);
19
19
  const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
20
20
  const specialFileNames = Object.keys(specialFileContents).map(file => ' - ' + file);
@@ -38,7 +38,7 @@ const init = async (options) => {
38
38
  `);
39
39
  }
40
40
  }
41
- const config = (0, command_utils_1.getConfig)();
41
+ const config = (0, command_utils_1.getConfig)(options);
42
42
  if (!config || !config?.primaryModel || !config?.models) {
43
43
  console.error('No cloving configuration found. Please run `cloving config`');
44
44
  return;
@@ -91,18 +91,24 @@ Here is an example response:
91
91
  {
92
92
  "name": "TypeScript",
93
93
  "version": "~> 5.5.3"
94
- "primary": true
94
+ "primary": true,
95
+ "directory": "src",
96
+ "extension": ".ts"
95
97
  },
96
98
  {
97
99
  "name": "JavaScript",
98
- "version": "ES6+"
100
+ "version": "ES6+",
101
+ "directory": "src",
102
+ "extension": ".js"
99
103
  }
100
104
  ],
101
105
  "frameworks": [
102
106
  {
103
107
  "name": "Node.js",
104
108
  "type": "Runtime environment",
105
- "primary": true
109
+ "primary": true,
110
+ "directory": "src",
111
+ "extension": ".js"
106
112
  }
107
113
  ],
108
114
  "testingFrameworks": [
@@ -10,7 +10,7 @@ const string_utils_1 = require("../utils/string_utils");
10
10
  const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
11
11
  const command_utils_1 = require("../utils/command_utils");
12
12
  const review = async (options) => {
13
- options.silent = (0, command_utils_1.getConfig)().silent || false;
13
+ options.silent = (0, command_utils_1.getConfig)(options).silent || false;
14
14
  const gpt = new cloving_gpt_1.default(options);
15
15
  try {
16
16
  // Define the prompt for analysis
@@ -9,27 +9,30 @@ const path_1 = __importDefault(require("path"));
9
9
  const child_process_1 = require("child_process");
10
10
  const git_utils_1 = require("../utils/git_utils");
11
11
  const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
12
- // Function to estimate token count
13
- const estimateTokens = async (text) => {
14
- const charCount = text.length;
15
- const tokenCount = Math.ceil(charCount / 4);
16
- return tokenCount;
12
+ const command_utils_1 = require("../utils/command_utils");
13
+ const extractChangedFiles = (gitDiff) => {
14
+ const fileRegex = /diff --git a\/(.+?) b\/(.+?)\n/g;
15
+ const files = new Set();
16
+ let match;
17
+ while ((match = fileRegex.exec(gitDiff)) !== null) {
18
+ files.add(match[1]);
19
+ }
20
+ return Array.from(files);
17
21
  };
18
- // Main function for the unit_tests command
19
- const unitTests = async (options) => {
20
- const gpt = new cloving_gpt_1.default(options);
21
- // Generate a temporary file path
22
- const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `unit_tests_${Date.now()}.tmp`);
23
- try {
24
- // Generate the prompt for the AI chat model
25
- const gitDiff = await (0, git_utils_1.getGitDiff)();
26
- const modelFiles = (0, child_process_1.execFileSync)('find', ['app/models', '-type', 'f']).toString().trim();
27
- const testFiles = (0, child_process_1.execFileSync)('find', ['spec', '-type', 'f']).toString().trim();
28
- const prompt = `Here is a list of all my model files:
22
+ const generatePrompt = (files, srcFiles, testFiles, gitDiff) => {
23
+ const filesList = files.length > 0 ? files.join('\n') : 'No files provided';
24
+ const diffSection = gitDiff ? `Here is my git diff:\n\n==== begin git diff ====\n${gitDiff}\n==== end git diff ====\n` : '';
25
+ return `Here is a list of code files I want to generate unit tests for:
26
+
27
+ ==== begin list of files ====
28
+ ${filesList}
29
+ ==== end list of files ====
29
30
 
30
- ==== begin list of models ====
31
- ${modelFiles}
32
- ==== end list of models ====
31
+ Here is a list of all my source files:
32
+
33
+ ==== begin list of source files ====
34
+ ${srcFiles}
35
+ ==== end list of source files ====
33
36
 
34
37
  Here is a list of all my test files:
35
38
 
@@ -37,12 +40,11 @@ Here is a list of all my test files:
37
40
  ${testFiles}
38
41
  ==== end list of test files ====
39
42
 
40
- Please enumerate all the files in this git diff as well as the file names of any models that
41
- this code interacts with. Also, list any test files that might be relevant to these changes:
43
+ ${diffSection}
44
+
45
+ Please enumerate all the files in the provided list${gitDiff ? ' and git diff' : ''} as well as the file names of anything that these files interact with.
42
46
 
43
- ==== begin git diff ====
44
- ${gitDiff}
45
- ==== end git diff ====
47
+ Also, list any test files that might be relevant to these files.
46
48
 
47
49
  Give me this output format for your answer:
48
50
 
@@ -51,69 +53,87 @@ Give me this output format for your answer:
51
53
  app/models/foo.rb
52
54
  app/views/baz/bar.html.erb
53
55
 
54
- ## interacted models
55
-
56
- app/models/pop.rb
57
-
58
56
  ## relevant test files
59
57
 
60
58
  test/models/foo_test.rb
61
59
  test/controllers/baz_controller_test.rb`;
62
- // Estimate and print token count
63
- let tokenCount = await estimateTokens(prompt);
64
- console.log(`Estimated token count: ${tokenCount}`);
65
- // Instantiate ClovingGPT and get the AI chat response
66
- const gpt = new cloving_gpt_1.default();
67
- const aiChatResponse = await gpt.generateText({ prompt });
68
- fs_1.default.writeFileSync(tempFilePath, aiChatResponse);
69
- // Handle "files" argument
70
- if (process.argv.includes('files')) {
71
- console.log(aiChatResponse);
72
- fs_1.default.unlinkSync(tempFilePath);
73
- process.exit(0);
60
+ };
61
+ const unitTests = async (options) => {
62
+ const { files } = options;
63
+ const gpt = new cloving_gpt_1.default(options);
64
+ // Read the cloving.json file
65
+ const config = (0, command_utils_1.readClovingConfig)();
66
+ const testingDirectory = config.testingFrameworks.find((framework) => framework.directory)?.directory || 'spec';
67
+ // Generate a temporary file path
68
+ const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `unit_tests_${Date.now()}.tmp`);
69
+ let allSrcFiles = [];
70
+ // Collect srcFiles for each language with specified directory and extension
71
+ for (const language of config.languages) {
72
+ if (language.directory && language.extension) {
73
+ const srcFiles = (0, child_process_1.execFileSync)('find', [language.directory, '-type', 'f', '-name', `*${language.extension}`]).toString().trim().split('\n');
74
+ allSrcFiles = allSrcFiles.concat(srcFiles);
75
+ }
76
+ }
77
+ // Ensure allSrcFiles is unique
78
+ allSrcFiles = Array.from(new Set(allSrcFiles));
79
+ if (allSrcFiles.length === 0) {
80
+ console.error('Could not find any source files to generate unit tests. Please run: cloving init');
81
+ process.exit(1);
82
+ }
83
+ const testFiles = (0, child_process_1.execFileSync)('find', [testingDirectory, '-type', 'f']).toString().trim();
84
+ let contextFiles;
85
+ if (files) {
86
+ const prompt = generatePrompt(files, allSrcFiles.join('\n'), testFiles);
87
+ contextFiles = await gpt.generateText({ prompt });
88
+ }
89
+ else {
90
+ const gitDiff = await (0, git_utils_1.getGitDiff)();
91
+ const changedFiles = extractChangedFiles(gitDiff);
92
+ const prompt = generatePrompt(changedFiles, allSrcFiles.join('\n'), testFiles, gitDiff);
93
+ contextFiles = await gpt.generateText({ prompt });
94
+ }
95
+ // Initialize variables
96
+ const lines = [];
97
+ const context = [];
98
+ // Read input from temporary file
99
+ contextFiles.split("\n").forEach((line) => {
100
+ if (line.trim()) {
101
+ lines.push(line.trim());
74
102
  }
75
- // Initialize variables
76
- const lines = [];
77
- // Read input from temporary file
78
- const fileContent = fs_1.default.readFileSync(tempFilePath, 'utf-8');
79
- fileContent.split('\n').forEach((line) => {
80
- if (line.trim()) {
81
- lines.push(line.trim());
82
- }
83
- });
84
- // Generate output
85
- fs_1.default.writeFileSync(tempFilePath, '## files\n\n');
86
- lines.forEach((line) => {
87
- if (fs_1.default.existsSync(line) && fs_1.default.statSync(line).isFile()) {
88
- const fileContents = fs_1.default.readFileSync(line, 'utf-8');
89
- fs_1.default.appendFileSync(tempFilePath, `### **Contents of ${line}**\n\n${fileContents}\n\n`);
90
- }
91
- });
92
- // Handle "code" argument
93
- if (process.argv.includes('code')) {
94
- console.log(fs_1.default.readFileSync(tempFilePath, 'utf-8'));
95
- fs_1.default.unlinkSync(tempFilePath);
96
- process.exit(0);
103
+ });
104
+ lines.forEach((codeFile) => {
105
+ if (fs_1.default.existsSync(codeFile) && fs_1.default.statSync(codeFile).isFile()) {
106
+ const fileContents = fs_1.default.readFileSync(codeFile, 'utf-8');
107
+ context.push(`### **Contents of ${codeFile}**\n\n${fileContents}\n\n### **End of ${codeFile}**`);
97
108
  }
98
- // Generate the message for unit test creation
109
+ });
110
+ // Generate the message for unit test creation
111
+ let analysis = '';
112
+ if ((files || []).length > 0) {
113
+ const message = `please create unit tests for these files:
114
+
115
+ ## begin list of files
116
+ ${(files || []).join("\n")}
117
+ ## end list of files
118
+
119
+ ## context
120
+ ${context.join("\n\n")}`;
121
+ // Get the model and analysis using ClovingGPT
122
+ analysis = await gpt.generateText({ prompt: message });
123
+ }
124
+ else {
99
125
  const message = `please create unit tests for these changes:
100
126
 
127
+ ## begin diff
101
128
  ${await (0, git_utils_1.getGitDiff)()}
129
+ ## end diff
102
130
 
103
131
  ## context
104
132
 
105
- ${fs_1.default.readFileSync(tempFilePath, 'utf-8')}`;
106
- tokenCount = await estimateTokens(message);
107
- console.log(`Estimated token count: ${tokenCount}`);
108
- // Clean up
109
- fs_1.default.unlinkSync(tempFilePath);
133
+ ${context.join("\n\n")}`;
110
134
  // Get the model and analysis using ClovingGPT
111
- const analysis = await gpt.generateText({ prompt: message });
112
- // Print the output
113
- console.log(analysis);
114
- }
115
- catch (error) {
116
- console.error('Error processing unit tests:', error.message);
135
+ analysis = await gpt.generateText({ prompt: message });
117
136
  }
137
+ console.log(analysis);
118
138
  };
119
139
  exports.default = unitTests;
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ program
25
25
  .command('unit-tests')
26
26
  .description('Generate unit tests for the changes')
27
27
  .option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
28
+ .option('-f, --files <filenames...>', 'Specify filenames for the unit tests')
28
29
  .action(unit_tests_1.default);
29
30
  program
30
31
  .command('models')
@@ -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.getConfig = exports.saveConfig = exports.fetchModels = exports.promptUser = exports.runCommand = exports.generateFileList = exports.checkForSpecialFiles = exports.collectSpecialFileContents = exports.SPECIAL_FILES = exports.CONFIG_PATH = void 0;
6
+ exports.readClovingConfig = exports.getConfig = exports.saveConfig = exports.fetchModels = exports.promptUser = exports.runCommand = exports.generateFileList = exports.checkForSpecialFiles = exports.collectSpecialFileContents = exports.SPECIAL_FILES = exports.CONFIG_PATH = void 0;
7
7
  const child_process_1 = require("child_process");
8
8
  const readline_1 = __importDefault(require("readline"));
9
9
  const fs_1 = __importDefault(require("fs"));
@@ -127,11 +127,17 @@ const saveConfig = (config) => {
127
127
  console.log(`Configuration saved to ${exports.CONFIG_PATH}`);
128
128
  };
129
129
  exports.saveConfig = saveConfig;
130
- const getConfig = () => {
130
+ const getConfig = (options) => {
131
131
  try {
132
132
  if (fs_1.default.existsSync(exports.CONFIG_PATH)) {
133
133
  const rawConfig = fs_1.default.readFileSync(exports.CONFIG_PATH, 'utf-8');
134
- return JSON.parse(rawConfig);
134
+ const config = JSON.parse(rawConfig);
135
+ if (options.silent) {
136
+ return { ...config, silent: true };
137
+ }
138
+ else {
139
+ return config;
140
+ }
135
141
  }
136
142
  }
137
143
  catch (err) {
@@ -141,11 +147,22 @@ const getConfig = () => {
141
147
  return {
142
148
  models: { [`${process.env.CLOVING_MODEL}`]: process.env.CLOVING_API_KEY ?? '' },
143
149
  primaryModel: process.env.CLOVING_MODEL,
144
- silent: false
150
+ silent: options.silent || false
145
151
  };
146
152
  }
147
153
  else {
148
- return { models: {}, primaryModel: null, silent: false };
154
+ return { models: {}, primaryModel: null, silent: options.silent || false };
149
155
  }
150
156
  };
151
157
  exports.getConfig = getConfig;
158
+ const readClovingConfig = () => {
159
+ const configPath = path_1.default.resolve(process.cwd(), 'cloving.json');
160
+ if (fs_1.default.existsSync(configPath)) {
161
+ const configFile = fs_1.default.readFileSync(configPath, 'utf-8');
162
+ return JSON.parse(configFile);
163
+ }
164
+ else {
165
+ throw new Error('cloving.json file not found');
166
+ }
167
+ };
168
+ exports.readClovingConfig = readClovingConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {