cloving 0.3.5 → 0.3.7

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
@@ -1,6 +1,12 @@
1
- # Cloving - CLI Code Generation with AI
1
+ # Cloving
2
2
 
3
- Cloving CLI is a command line utility which integrates AI into your development workflow. It provides AI-powered code generation, commit message generation, code reviews, unit test generation, and more.
3
+ An AI pair-programmer for the command line.
4
+
5
+ LLM-powered shell commands, code generation, commit messages, code reviews, unit test generation, and more. Works with most popular LLMs including OpenAI, Claude, Gemini, Ollama, and more.
6
+
7
+ [![asciicast](https://asciinema.org/a/SCcxw4B2XFQaTD0GeDTk0xbuq.svg)](https://asciinema.org/a/SCcxw4B2XFQaTD0GeDTk0xbuq)
8
+
9
+ Cloving CLI is a command line utility which integrates AI into your development workflow.
4
10
 
5
11
  What makes the Cloving CLI different from other AI tools is that it builds sophisticated context-aware prompts to generate code, commit messages, and more.
6
12
 
@@ -169,7 +169,7 @@ class ClovingGPT {
169
169
  if (errorMessage === '') {
170
170
  errorMessage = 'connection error';
171
171
  }
172
- console.error(`Error communicating with the GPT server (${this.adapter.getEndpoint(this.stream)})\n\n${JSON.stringify(error.response?.data || "''", null, 2)}\n`);
172
+ console.error(`Error communicating with the GPT server (${this.adapter.getEndpoint(this.stream)})\n\n${colors_1.default.red.bold(JSON.stringify(error.response?.data || "''", null, 2))}\n`);
173
173
  throw error;
174
174
  }
175
175
  }
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.config = void 0;
4
4
  const config_utils_1 = require("../utils/config_utils");
5
- const command_utils_1 = require("../utils/command_utils");
5
+ const prompt_utils_1 = require("../utils/prompt_utils");
6
6
  const prompts_1 = require("@inquirer/prompts");
7
7
  const config = async () => {
8
8
  let currentConfig = (0, config_utils_1.getConfig)({});
@@ -10,7 +10,7 @@ const config = async () => {
10
10
  if (!currentConfig.models) {
11
11
  currentConfig.models = {};
12
12
  }
13
- const models = await (0, command_utils_1.fetchModels)();
13
+ const models = await (0, prompt_utils_1.fetchModels)();
14
14
  if (models.length === 0) {
15
15
  console.error('No models available.');
16
16
  throw new Error('No models available');
@@ -8,10 +8,10 @@ const copy_paste_1 = __importDefault(require("copy-paste"));
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const prompts_1 = require("@inquirer/prompts");
11
- const command_utils_1 = require("../../utils/command_utils");
11
+ const prompt_utils_1 = require("../../utils/prompt_utils");
12
12
  const config_utils_1 = require("../../utils/config_utils");
13
13
  const generateContextPrompt = (files, contextFiles) => {
14
- const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
14
+ const specialFileContents = (0, prompt_utils_1.collectSpecialFileContents)();
15
15
  const specialFiles = Object.keys(specialFileContents)
16
16
  .map((file) => `### Contents of ${file}\n\n${JSON.stringify(specialFileContents[file], null, 2)}\n\n`)
17
17
  .join('\n');
@@ -55,7 +55,7 @@ const context = async (options) => {
55
55
  message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
56
56
  });
57
57
  if (contextFile) {
58
- contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, contextFiles, options);
58
+ contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(contextFile, contextFiles, options);
59
59
  }
60
60
  else {
61
61
  includeMoreFiles = false;
@@ -4,225 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.init = void 0;
7
- const fs_1 = __importDefault(require("fs"));
8
- const os_1 = __importDefault(require("os"));
9
- const path_1 = __importDefault(require("path"));
10
- const prompts_1 = require("@inquirer/prompts");
11
- const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
12
- const ignore_1 = __importDefault(require("ignore"));
13
- const string_utils_1 = require("../utils/string_utils");
14
- const config_utils_1 = require("../utils/config_utils");
15
- const command_utils_1 = require("../utils/command_utils");
16
- // Main function for the describe command
7
+ const InitManager_1 = __importDefault(require("../managers/InitManager"));
17
8
  const init = async (options) => {
18
- options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
19
- const gpt = new cloving_gpt_1.default(options);
20
- const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
21
- const specialFileNames = Object.keys(specialFileContents).map((file) => ' - ' + file);
22
- // Initialize chat history
23
- const chatHistory = [
24
- {
25
- role: 'system',
26
- content: `You are an AI assistant helping to initialize a project by analyzing its structure and technologies.
27
-
28
- Please return JSON-formatted metadata about the project, including:
29
-
30
- - The programming language(s) used
31
- - The detected framework(s) (if any)
32
- - The version of the language(s)
33
-
34
- Here is a typescript interface for the expected response:
35
-
36
- interface LanguageConfig {
37
- name: string
38
- version?: string
39
- primary?: boolean
40
- directory: string
41
- extension: string
42
- }
43
-
44
- interface FrameworkConfig {
45
- name: string
46
- type: string
47
- version?: string
48
- primary?: boolean
49
- directory?: string
50
- extension?: string
51
- }
52
-
53
- interface TestingFrameworkConfig {
54
- name: string
55
- type: string
56
- version?: string
57
- directory?: string
58
- }
59
-
60
- interface BuildToolConfig {
61
- name: string
62
- type: string
63
- version?: string
64
- }
65
-
66
- interface LinterConfig {
67
- name: string
68
- version?: string
69
- type?: string
70
- }
71
-
72
- interface DatabaseConfig {
73
- name: string
74
- primary?: boolean
75
- }
76
-
77
- export interface ClovingfileConfig {
78
- languages: LanguageConfig[]
79
- frameworks: FrameworkConfig[]
80
- testingFrameworks?: TestingFrameworkConfig[]
81
- buildTools: BuildToolConfig[]
82
- packageManager: string
83
- linters: LinterConfig[]
84
- databases?: DatabaseConfig[]
85
- projectType: string
86
- }
87
-
88
- Here is an example response:
89
-
90
- {
91
- "languages": [
92
- {
93
- "name": "TypeScript",
94
- "version": "~> 5.5.3"
95
- "primary": true,
96
- "directory": "src",
97
- "extension": ".ts"
98
- },
99
- {
100
- "name": "JavaScript",
101
- "version": "ES6+",
102
- "directory": "src",
103
- "extension": ".js"
104
- }
105
- ],
106
- "frameworks": [
107
- {
108
- "name": "Node.js",
109
- "type": "Runtime environment",
110
- "primary": true,
111
- "directory": "src",
112
- "extension": ".js"
113
- }
114
- ],
115
- "testingFrameworks": [
116
- {
117
- "name": "Jest",
118
- "type": "Testing framework",
119
- "version": "29.7.0"
120
- "directory": "tests"
121
- },
122
- ],
123
- "buildTools": [
124
- {
125
- "name": "TypeScript Compiler (tsc)",
126
- "type": "Transpiler"
127
- },
128
- {
129
- "name": "Vite",
130
- "type": "Build tool",
131
- "version": "5.3.3"
132
- }
133
- ],
134
- "packageManager": "Yarn",
135
- "linters": [
136
- {
137
- "name": "ESLint",
138
- "version": "9.6.0"
139
- }
140
- ],
141
- "databases": [
142
- {
143
- "name": "MongoDB",
144
- "version": "5.0.3",
145
- "primary": true
146
- }
147
- ],
148
- "projectType": "Command-line tool",
149
- }`,
150
- },
151
- ];
152
- if (!options.silent) {
153
- if (specialFileNames.length > 0) {
154
- console.log(`Cloving will analyze the list of files and the contents of the following files:
155
-
156
- ${specialFileNames.join('\n')}
157
-
158
- Cloving will send AI a request to summarize the technologies used in this project.
159
-
160
- This will provide better context for future Cloving requests.`);
161
- }
162
- else {
163
- console.log(`
164
- This script will analyze the list of files in the current directory using GPT to summarize the
165
- technologies used. This will provide better context for future Cloving requests.
166
- `);
167
- }
168
- }
169
- const config = (0, config_utils_1.getConfig)(options);
170
- if (!config || !config?.models) {
171
- console.error('No cloving configuration found. Please run `cloving config`');
172
- return;
173
- }
174
- if (!(0, command_utils_1.checkForSpecialFiles)()) {
175
- console.error('No dependencies files detected. Please add a dependency file (e.g. package.json, Gemfile, requirements.txt, etc.) to your project and run `cloving init` again.');
176
- return;
177
- }
178
- const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `describe_${Date.now()}.tmp`);
179
- try {
180
- const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
181
- const ig = (0, ignore_1.default)();
182
- if (fs_1.default.existsSync(gitignorePath)) {
183
- const gitignoreContent = fs_1.default.readFileSync(gitignorePath, 'utf-8');
184
- ig.add(gitignoreContent);
185
- }
186
- const fileList = await (0, command_utils_1.generateFileList)();
187
- const filteredFileList = fileList.filter((file) => {
188
- try {
189
- return !ig.ignores(file);
190
- }
191
- catch (error) {
192
- return false;
193
- }
194
- });
195
- const limitedFileList = filteredFileList.slice(0, 100);
196
- const projectDetails = {
197
- files: limitedFileList,
198
- specialFiles: specialFileContents,
199
- };
200
- const prompt = `Here is a JSON object describing my project:
201
- ${JSON.stringify(projectDetails, null, 2)}`;
202
- chatHistory.push({ role: 'user', content: prompt });
203
- const aiChatResponse = await gpt.generateText({ prompt, messages: chatHistory });
204
- chatHistory.push({ role: 'assistant', content: aiChatResponse });
205
- const cleanAiChatResponse = (0, string_utils_1.extractJsonMetadata)(aiChatResponse);
206
- fs_1.default.writeFileSync(tempFilePath, cleanAiChatResponse);
207
- // Save the AI chat response to cloving.json
208
- fs_1.default.writeFileSync('cloving.json', cleanAiChatResponse);
209
- console.log('Project data saved to cloving.json');
210
- // Prompt the user if they want to review the generated cloving.json
211
- if (!options.silent) {
212
- const reviewAnswer = await (0, prompts_1.confirm)({
213
- message: 'Do you want to review the generated data?',
214
- default: true,
215
- });
216
- if (reviewAnswer) {
217
- console.log(cleanAiChatResponse);
218
- }
219
- }
220
- // Clean up
221
- fs_1.default.unlinkSync(tempFilePath);
222
- }
223
- catch (error) {
224
- console.error('Error describing the project:', error.message);
225
- }
9
+ const initManager = new InitManager_1.default(options);
10
+ await initManager.initialize();
226
11
  };
227
12
  exports.init = init;
228
13
  exports.default = exports.init;
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const fs_1 = __importDefault(require("fs"));
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const config_utils_1 = require("../utils/config_utils");
9
- const command_utils_1 = require("../utils/command_utils");
9
+ const prompt_utils_1 = require("../utils/prompt_utils");
10
10
  const tokens = async (options) => {
11
11
  let contextFiles = {};
12
12
  let files = options.files || '.';
@@ -18,7 +18,7 @@ const tokens = async (options) => {
18
18
  .stat(filePath)
19
19
  .then((stat) => stat.isDirectory())
20
20
  .catch(() => false)) {
21
- const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
21
+ const dirFiles = await (0, prompt_utils_1.getAllFilesInDirectory)(filePath);
22
22
  expandedFiles = expandedFiles.concat(dirFiles.map((f) => path_1.default.relative(process.cwd(), f)));
23
23
  }
24
24
  else {
package/dist/index.js CHANGED
@@ -17,12 +17,12 @@ const commit_1 = __importDefault(require("./commands/generate/commit"));
17
17
  const context_1 = __importDefault(require("./commands/generate/context"));
18
18
  const unit_tests_1 = __importDefault(require("./commands/generate/unit_tests"));
19
19
  const review_1 = __importDefault(require("./commands/generate/review"));
20
- const command_utils_1 = require("./utils/command_utils");
20
+ const prompt_utils_1 = require("./utils/prompt_utils");
21
21
  const program = new commander_1.Command();
22
22
  program
23
23
  .name('cloving')
24
24
  .description('Integrate AI into your development workflow for generating commit messages, code reviews, and unit tests.')
25
- .version((0, command_utils_1.getPackageVersion)());
25
+ .version((0, prompt_utils_1.getPackageVersion)());
26
26
  program
27
27
  .command('config')
28
28
  .description('Configure cloving with your API key and models to use')
@@ -71,6 +71,7 @@ generate
71
71
  .command('shell')
72
72
  .alias('sh')
73
73
  .description('Generate a shell command based on a prompt')
74
+ .option('-e, --exec', 'Automatically execute the generated shell command')
74
75
  .option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
75
76
  .option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5-sonnet-20240620)')
76
77
  .option('-p, --prompt <prompt>', 'Specify the prompt to use')
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const events_1 = require("events");
4
+ // Passes through string chunks, looking for code blocks, when it finds one, it starts buffering until it finds the end of the block
5
+ // Then it emits the code block and clears the buffer and continues
6
+ class BlockManager extends events_1.EventEmitter {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.buffer = '';
10
+ this.codeBuffer = '';
11
+ this.isBufferingCode = false;
12
+ this.isWaitingForContent = false;
13
+ }
14
+ // add content to the buffer, watching for code blocks along the way
15
+ addContent(content) {
16
+ // when getting ` , just add it to the buffer until we get a char that isn't `
17
+ if (content.includes('`')) {
18
+ this.isWaitingForContent = true;
19
+ this.buffer += content;
20
+ return;
21
+ }
22
+ else if (this.isWaitingForContent) {
23
+ content = this.buffer + content;
24
+ this.buffer = '';
25
+ }
26
+ const codeBlockMarker = '\n```';
27
+ let markerIndex = content.indexOf(codeBlockMarker);
28
+ if (content.startsWith('```'))
29
+ markerIndex = 0;
30
+ if (markerIndex !== -1) {
31
+ if (this.isBufferingCode) {
32
+ // End of code block
33
+ this.codeBuffer += content.slice(0, markerIndex + codeBlockMarker.length);
34
+ const rest = content.slice(markerIndex + codeBlockMarker.length);
35
+ this.emit('endGeneratingCode');
36
+ this.emitCodeBlock();
37
+ this.emit('content', rest);
38
+ }
39
+ else {
40
+ // Start of code block
41
+ this.emitBuffer(content.slice(0, markerIndex));
42
+ this.codeBuffer = content.slice(markerIndex);
43
+ this.emit('startGeneratingCode');
44
+ this.isBufferingCode = true;
45
+ }
46
+ }
47
+ else {
48
+ // No code block marker found
49
+ if (this.isBufferingCode) {
50
+ this.codeBuffer += content;
51
+ }
52
+ else {
53
+ this.buffer += content;
54
+ }
55
+ }
56
+ if (!this.isBufferingCode && this.buffer) {
57
+ this.emitBuffer();
58
+ }
59
+ }
60
+ emitBuffer(additionalContent = '') {
61
+ const contentToEmit = this.buffer + additionalContent;
62
+ if (contentToEmit) {
63
+ this.emit('content', contentToEmit);
64
+ this.clearBuffer();
65
+ }
66
+ }
67
+ emitCodeBlock() {
68
+ if (this.codeBuffer.length > 0) {
69
+ const codeParts = this.parseCodeBuffer();
70
+ if (codeParts) {
71
+ const { language, currentStart, newEnd, currentCode, newCode } = codeParts;
72
+ this.emit('codeBlock', {
73
+ language,
74
+ currentStart,
75
+ newEnd,
76
+ currentCode,
77
+ newCode,
78
+ raw: this.codeBuffer,
79
+ });
80
+ this.emit('content', this.buffer);
81
+ this.clearBuffer();
82
+ }
83
+ else {
84
+ this.emit('content', this.codeBuffer);
85
+ this.clearBuffer();
86
+ }
87
+ }
88
+ }
89
+ /**
90
+ * Parses a code buffer into its constituent parts.
91
+ *
92
+ * This function takes a code buffer as a string, splits it into lines, and identifies the language,
93
+ * current start index, divider index, and new end index. It then extracts the current code and new code
94
+ * from the buffer based on these indices.
95
+ *
96
+ * @return {{ language: string, currentStart: string, newEnd: string, current: string, newCode: string, afterCode: string }}
97
+ * An object containing the language, current start, new end, current code, and new code.
98
+ */
99
+ parseCodeBuffer() {
100
+ const lines = this.codeBuffer.split('\n');
101
+ const language = lines[0].trim().slice(3); // Remove '```' from the language line
102
+ const currentStartIndex = lines.findIndex((line) => line.startsWith('<<<<<<< CURRENT'));
103
+ const dividerIndex = lines.findIndex((line) => line === '=======');
104
+ const newEndIndex = lines.findIndex((line) => line.startsWith('>>>>>>> NEW'));
105
+ if (currentStartIndex === -1 || dividerIndex === -1 || newEndIndex === -1) {
106
+ return null;
107
+ }
108
+ const currentCode = lines.slice(currentStartIndex + 1, dividerIndex).join('\n');
109
+ const newCode = lines.slice(dividerIndex + 1, newEndIndex).join('\n');
110
+ return {
111
+ language,
112
+ currentStart: lines[currentStartIndex],
113
+ newEnd: lines[newEndIndex],
114
+ currentCode,
115
+ newCode,
116
+ };
117
+ }
118
+ clearBuffer() {
119
+ this.buffer = '';
120
+ this.codeBuffer = '';
121
+ this.isBufferingCode = false;
122
+ this.isWaitingForContent = false;
123
+ }
124
+ }
125
+ exports.default = BlockManager;