cloving 0.1.0
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/LICENSE.md +21 -0
- package/README.md +119 -0
- package/dist/abstract_gpt/index.js +55 -0
- package/dist/cloving_gpt/adapters/Adapter.js +2 -0
- package/dist/cloving_gpt/adapters/claude.js +60 -0
- package/dist/cloving_gpt/adapters/gpt4all.js +29 -0
- package/dist/cloving_gpt/adapters/index.js +2 -0
- package/dist/cloving_gpt/adapters/ollama.js +67 -0
- package/dist/cloving_gpt/adapters/openai.js +41 -0
- package/dist/cloving_gpt/index.js +116 -0
- package/dist/commands/analyze.js +47 -0
- package/dist/commands/check.js +7 -0
- package/dist/commands/commit.js +44 -0
- package/dist/commands/config.js +40 -0
- package/dist/commands/describe.js +235 -0
- package/dist/commands/init.js +169 -0
- package/dist/commands/models.js +17 -0
- package/dist/commands/review.js +50 -0
- package/dist/commands/unit_tests.js +119 -0
- package/dist/commands/update.js +7 -0
- package/dist/index.js +47 -0
- package/dist/utils/command_utils.js +151 -0
- package/dist/utils/gitUtils.js +42 -0
- package/dist/utils/git_utils.js +70 -0
- package/dist/utils/modelUtils.js +7 -0
- package/dist/utils/model_utils.js +7 -0
- package/dist/utils/string_utils.js +63 -0
- package/dist/utils/types.js +2 -0
- package/package.json +65 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Lucas Carlson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Cloving
|
|
2
|
+
|
|
3
|
+
Cloving is an innovative tool designed to seamlessly integrate AI into various aspects of the developer workflow. It aims to enhance productivity and code quality by leveraging AI for tasks such as generating commit messages, conducting code reviews, and creating unit tests.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **AI-powered Git commit message generation**
|
|
8
|
+
- **Automated code reviews with AI insights**
|
|
9
|
+
- **AI-assisted unit test generation**
|
|
10
|
+
- **Seamless integration with existing development workflows**
|
|
11
|
+
- **Customizable AI prompts for different tasks**
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- Node.js (version 20 or higher)
|
|
16
|
+
- Git
|
|
17
|
+
- API access to AI chat models (e.g., OpenAI GPT-4o, Claude, etc.)
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
1. Clone this repository:
|
|
22
|
+
```bash
|
|
23
|
+
git clone https://github.com/clovingai/cloving-cli.git
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. Navigate to the project directory:
|
|
27
|
+
```bash
|
|
28
|
+
cd cloving-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
3. Install dependencies:
|
|
32
|
+
```bash
|
|
33
|
+
yarn install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
4. Set up your AI chat model environment variables:
|
|
37
|
+
```bash
|
|
38
|
+
export CLOVING_MODEL=your_preferred_model
|
|
39
|
+
export CLOVING_API_KEY=your_api_key
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
Cloving provides several commands to help integrate AI into your development workflow:
|
|
45
|
+
|
|
46
|
+
### Generating Commit Messages
|
|
47
|
+
|
|
48
|
+
Run the following command in your Git repository:
|
|
49
|
+
```bash
|
|
50
|
+
cloving commit
|
|
51
|
+
```
|
|
52
|
+
This will generate an AI-powered commit message based on your recent changes, allow you to review and edit it, and then commit your changes.
|
|
53
|
+
|
|
54
|
+
### Code Reviews
|
|
55
|
+
|
|
56
|
+
To get an AI-powered code review for your latest changes:
|
|
57
|
+
```bash
|
|
58
|
+
cloving review
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Generating Unit Tests
|
|
62
|
+
|
|
63
|
+
To generate unit tests for a specific file or function:
|
|
64
|
+
```bash
|
|
65
|
+
cloving unit-tests [file_path_to_test]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Initial Setup
|
|
69
|
+
|
|
70
|
+
To set up Cloving in the current project, generating a `cloving.json` file that includes a GPT-generated project overview with details like the language, framework, and language version:
|
|
71
|
+
```bash
|
|
72
|
+
cloving init
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Configuration
|
|
76
|
+
|
|
77
|
+
To configure Cloving with your API key and model:
|
|
78
|
+
```bash
|
|
79
|
+
cloving config
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Listing Available Models
|
|
83
|
+
|
|
84
|
+
To list the available AI models:
|
|
85
|
+
```bash
|
|
86
|
+
cloving models
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Command Line Options
|
|
90
|
+
|
|
91
|
+
Usage: `cloving [options] [command]`
|
|
92
|
+
|
|
93
|
+
Integrate AI into your development workflow for generating commit messages, code reviews, and unit tests.
|
|
94
|
+
|
|
95
|
+
Options:
|
|
96
|
+
- `-V, --version` output the version number
|
|
97
|
+
- `-h, --help` display help for command
|
|
98
|
+
|
|
99
|
+
Commands:
|
|
100
|
+
- `commit` Generate a commit message and commit the changes
|
|
101
|
+
- `unit-tests` Generate unit tests for the changes
|
|
102
|
+
- `models` List available models
|
|
103
|
+
- `review` Review the changes and propose
|
|
104
|
+
- `init` Setup Cloving in the current project
|
|
105
|
+
- `config` Configure Cloving with your API key and model
|
|
106
|
+
- `help [command]` Display help for command
|
|
107
|
+
|
|
108
|
+
## Contributing
|
|
109
|
+
|
|
110
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
115
|
+
|
|
116
|
+
## Acknowledgements
|
|
117
|
+
|
|
118
|
+
- This project is inspired by [aichat](https://github.com/sigoden/aichat) for interfacing with AI models.
|
|
119
|
+
- Special thanks to the open-source community for inspiration and support.
|
|
@@ -0,0 +1,55 @@
|
|
|
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 axios_1 = __importDefault(require("axios"));
|
|
7
|
+
class AbstractGPT {
|
|
8
|
+
constructor() {
|
|
9
|
+
const clovingModel = process.env.CLOVING_MODEL;
|
|
10
|
+
this.apiKey = process.env.CLOVING_API_KEY || '';
|
|
11
|
+
if (!clovingModel || !this.apiKey) {
|
|
12
|
+
throw new Error("CLOVING_MODEL and CLOVING_API_KEY environment variables must be set");
|
|
13
|
+
}
|
|
14
|
+
const [provider, model] = clovingModel.split(':');
|
|
15
|
+
this.provider = provider;
|
|
16
|
+
this.model = model;
|
|
17
|
+
}
|
|
18
|
+
getEndpoint() {
|
|
19
|
+
switch (this.provider) {
|
|
20
|
+
case 'openai':
|
|
21
|
+
return `https://api.openai.com/v1/engines/${this.model}/completions`;
|
|
22
|
+
case 'claude':
|
|
23
|
+
return `https://api.anthropic.com/v1/claude/${this.model}`;
|
|
24
|
+
case 'gpt4all':
|
|
25
|
+
return `https://api.gpt4all.io/v1/models/${this.model}/completions`;
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unsupported provider: ${this.provider}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async generateText(request) {
|
|
31
|
+
const endpoint = this.getEndpoint();
|
|
32
|
+
const response = await axios_1.default.post(endpoint, {
|
|
33
|
+
model: this.model,
|
|
34
|
+
prompt: request.prompt,
|
|
35
|
+
max_tokens: request.maxTokens || 100,
|
|
36
|
+
temperature: request.temperature || 0.7
|
|
37
|
+
}, {
|
|
38
|
+
headers: {
|
|
39
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
switch (this.provider) {
|
|
44
|
+
case 'openai':
|
|
45
|
+
return response.data.choices[0].text;
|
|
46
|
+
case 'claude':
|
|
47
|
+
return response.data.completion;
|
|
48
|
+
case 'gpt4all':
|
|
49
|
+
return response.data.text;
|
|
50
|
+
default:
|
|
51
|
+
throw new Error(`Unsupported provider: ${this.provider}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.default = AbstractGPT;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClaudeAdapter = void 0;
|
|
4
|
+
class ClaudeAdapter {
|
|
5
|
+
constructor(model) {
|
|
6
|
+
this.model = model;
|
|
7
|
+
this.anthropicVersion = '2023-06-01';
|
|
8
|
+
}
|
|
9
|
+
static listSupportedModels() {
|
|
10
|
+
ClaudeAdapter.supportedModels.forEach(model => {
|
|
11
|
+
console.log(model);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
getEndpoint() {
|
|
15
|
+
return `https://api.anthropic.com/v1/messages`;
|
|
16
|
+
}
|
|
17
|
+
getHeaders(apiKey) {
|
|
18
|
+
return {
|
|
19
|
+
'x-api-key': apiKey,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'anthropic-version': this.anthropicVersion
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
getPayload(request) {
|
|
25
|
+
return {
|
|
26
|
+
model: this.model,
|
|
27
|
+
system: 'You are a computer programmer giving advice on how to write better code.',
|
|
28
|
+
messages: [
|
|
29
|
+
{ role: 'user', content: request.prompt }
|
|
30
|
+
],
|
|
31
|
+
max_tokens: request.maxTokens || 4096,
|
|
32
|
+
temperature: request.temperature || 0.7
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
extractResponse(data) {
|
|
36
|
+
try {
|
|
37
|
+
if (data && data.content && Array.isArray(data.content) && data.content.length > 0) {
|
|
38
|
+
return data.content.map((item) => item.text).join('\n');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.error('Unexpected response structure:', data);
|
|
42
|
+
throw new Error('Invalid response structure');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('Error extracting response:', error);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.ClaudeAdapter = ClaudeAdapter;
|
|
52
|
+
ClaudeAdapter.supportedModels = [
|
|
53
|
+
'claude:claude-3-5-sonnet-20240620',
|
|
54
|
+
'claude:claude-3-opus-20240229',
|
|
55
|
+
'claude:claude-3-sonnet-20240229',
|
|
56
|
+
'claude:claude-3-haiku-20240307',
|
|
57
|
+
// Add more supported models here as needed
|
|
58
|
+
];
|
|
59
|
+
// Example usage:
|
|
60
|
+
// ClaudeAdapter.listSupportedModels()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GPT4AllAdapter = void 0;
|
|
4
|
+
class GPT4AllAdapter {
|
|
5
|
+
constructor(model) {
|
|
6
|
+
this.model = model;
|
|
7
|
+
}
|
|
8
|
+
getEndpoint() {
|
|
9
|
+
return `https://api.gpt4all.io/v1/models/${this.model}/completions`;
|
|
10
|
+
}
|
|
11
|
+
getHeaders(apiKey) {
|
|
12
|
+
return {
|
|
13
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
14
|
+
'Content-Type': 'application/json'
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
getPayload(request) {
|
|
18
|
+
return {
|
|
19
|
+
model: this.model,
|
|
20
|
+
prompt: request.prompt,
|
|
21
|
+
max_tokens: request.maxTokens,
|
|
22
|
+
temperature: request.temperature || 0.7
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
extractResponse(data) {
|
|
26
|
+
return data.text;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.GPT4AllAdapter = GPT4AllAdapter;
|
|
@@ -0,0 +1,67 @@
|
|
|
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.OllamaAdapter = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
class OllamaAdapter {
|
|
9
|
+
constructor(model) {
|
|
10
|
+
this.model = model;
|
|
11
|
+
}
|
|
12
|
+
static async listSupportedModels() {
|
|
13
|
+
try {
|
|
14
|
+
const endpoint = 'http://localhost:11434/api/tags';
|
|
15
|
+
const headers = {
|
|
16
|
+
'Content-Type': 'application/json'
|
|
17
|
+
};
|
|
18
|
+
const response = await axios_1.default.get(endpoint, { headers });
|
|
19
|
+
const data = response.data;
|
|
20
|
+
if (data && Array.isArray(data.models)) {
|
|
21
|
+
OllamaAdapter.supportedModels = data.models.map((model) => `ollama:${model.name}`);
|
|
22
|
+
OllamaAdapter.supportedModels.forEach(model => {
|
|
23
|
+
console.log(model);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.error('Unexpected response structure:', data);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
// do nothing, no ollama server running
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
getEndpoint() {
|
|
35
|
+
return `http://localhost:11434/api/generate`;
|
|
36
|
+
}
|
|
37
|
+
getHeaders(apiKey) {
|
|
38
|
+
return {
|
|
39
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
getPayload(request) {
|
|
44
|
+
return {
|
|
45
|
+
model: this.model,
|
|
46
|
+
prompt: request.prompt,
|
|
47
|
+
stream: request.stream ?? false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
extractResponse(data) {
|
|
51
|
+
try {
|
|
52
|
+
if (data && typeof data === 'object' && 'response' in data) {
|
|
53
|
+
return data.response;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.error('Unexpected response structure:', data);
|
|
57
|
+
throw new Error('Invalid response structure');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error('Error extracting response:', error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.OllamaAdapter = OllamaAdapter;
|
|
67
|
+
OllamaAdapter.supportedModels = [];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAIAdapter = void 0;
|
|
4
|
+
class OpenAIAdapter {
|
|
5
|
+
static listSupportedModels() {
|
|
6
|
+
OpenAIAdapter.supportedModels.forEach(model => {
|
|
7
|
+
console.log(model);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
constructor(model) {
|
|
11
|
+
this.model = model;
|
|
12
|
+
}
|
|
13
|
+
getEndpoint() {
|
|
14
|
+
return `https://api.openai.com/v1/chat/completions`;
|
|
15
|
+
}
|
|
16
|
+
getHeaders(apiKey) {
|
|
17
|
+
return {
|
|
18
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
19
|
+
'Content-Type': 'application/json'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
getPayload(request) {
|
|
23
|
+
return {
|
|
24
|
+
model: this.model,
|
|
25
|
+
messages: [{ role: "user", content: request.prompt }],
|
|
26
|
+
max_tokens: request.maxTokens,
|
|
27
|
+
temperature: request.temperature || 0.7
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
extractResponse(data) {
|
|
31
|
+
return data.choices[0].message.content;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.OpenAIAdapter = OpenAIAdapter;
|
|
35
|
+
OpenAIAdapter.supportedModels = [
|
|
36
|
+
'openai:gpt-4o',
|
|
37
|
+
'openai:gpt-4-turbo',
|
|
38
|
+
'openai:gpt-3.5-turbo',
|
|
39
|
+
'openai:text-embedding-3-large',
|
|
40
|
+
// Add more supported models here as needed
|
|
41
|
+
];
|
|
@@ -0,0 +1,116 @@
|
|
|
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 axios_1 = __importDefault(require("axios"));
|
|
7
|
+
const readline_1 = __importDefault(require("readline"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const process_1 = __importDefault(require("process"));
|
|
10
|
+
const claude_1 = require("./adapters/claude");
|
|
11
|
+
const openai_1 = require("./adapters/openai");
|
|
12
|
+
const gpt4all_1 = require("./adapters/gpt4all");
|
|
13
|
+
const ollama_1 = require("./adapters/ollama");
|
|
14
|
+
const command_utils_1 = require("../utils/command_utils");
|
|
15
|
+
class ClovingGPT {
|
|
16
|
+
constructor(options = { silent: false }) {
|
|
17
|
+
const config = (0, command_utils_1.getConfig)();
|
|
18
|
+
if (!config || !config.primaryModel || !config.models) {
|
|
19
|
+
throw new Error('No cloving configuration found. Please run `cloving config`');
|
|
20
|
+
}
|
|
21
|
+
const clovingModel = config.primaryModel;
|
|
22
|
+
this.apiKey = config.models[config?.primaryModel || ''].trim();
|
|
23
|
+
this.silent = options.silent;
|
|
24
|
+
const parts = clovingModel.split(':');
|
|
25
|
+
const model = parts.slice(1).join(':');
|
|
26
|
+
switch (parts[0]) {
|
|
27
|
+
case 'claude':
|
|
28
|
+
this.adapter = new claude_1.ClaudeAdapter(model);
|
|
29
|
+
break;
|
|
30
|
+
case 'openai':
|
|
31
|
+
this.adapter = new openai_1.OpenAIAdapter(model);
|
|
32
|
+
break;
|
|
33
|
+
case 'gpt4all':
|
|
34
|
+
this.adapter = new gpt4all_1.GPT4AllAdapter(model);
|
|
35
|
+
break;
|
|
36
|
+
case 'ollama':
|
|
37
|
+
this.adapter = new ollama_1.OllamaAdapter(model);
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(`Unsupported provider: ${parts[0]}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async askUserToConfirm(prompt, message) {
|
|
44
|
+
if (this.silent)
|
|
45
|
+
return true;
|
|
46
|
+
const rl = readline_1.default.createInterface({
|
|
47
|
+
input: process_1.default.stdin,
|
|
48
|
+
output: process_1.default.stdout
|
|
49
|
+
});
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
rl.question(message, (answer) => {
|
|
52
|
+
rl.close();
|
|
53
|
+
answer = answer.trim().toLowerCase();
|
|
54
|
+
if (answer === 'y' || answer === '') {
|
|
55
|
+
const less = (0, child_process_1.spawn)('less', ['-R'], { stdio: ['pipe', process_1.default.stdout, process_1.default.stderr] });
|
|
56
|
+
less.stdin.write(prompt);
|
|
57
|
+
less.stdin.end();
|
|
58
|
+
less.on('close', (code) => {
|
|
59
|
+
if (code !== 0) {
|
|
60
|
+
console.error('less command exited with an error.');
|
|
61
|
+
resolve(false);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const rlConfirm = readline_1.default.createInterface({
|
|
65
|
+
input: process_1.default.stdin,
|
|
66
|
+
output: process_1.default.stdout
|
|
67
|
+
});
|
|
68
|
+
rlConfirm.question('Do you still want to continue? [Yn]: ', (confirmAnswer) => {
|
|
69
|
+
rlConfirm.close();
|
|
70
|
+
confirmAnswer = confirmAnswer.trim().toLowerCase();
|
|
71
|
+
resolve(confirmAnswer === 'y' || confirmAnswer === '');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
less.stdin.on('error', (err) => {
|
|
75
|
+
if (isNodeError(err) && err.code === 'EPIPE') {
|
|
76
|
+
resolve(true);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.error('Pipeline error:', err);
|
|
80
|
+
resolve(false);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
resolve(true);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async generateText(request) {
|
|
91
|
+
const endpoint = this.adapter.getEndpoint();
|
|
92
|
+
const payload = this.adapter.getPayload(request);
|
|
93
|
+
const headers = this.adapter.getHeaders(this.apiKey);
|
|
94
|
+
const tokenCount = Math.ceil(request.prompt.length / 4).toLocaleString();
|
|
95
|
+
const shouldContinue = await this.askUserToConfirm(request.prompt, `Do you want to review the ~${tokenCount} token prompt before sending it to ${endpoint}? [Yn]: `);
|
|
96
|
+
if (!shouldContinue) {
|
|
97
|
+
throw 'Operation cancelled by the user.';
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const response = await axios_1.default.post(endpoint, payload, { headers });
|
|
101
|
+
return this.adapter.extractResponse(response.data);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
let errorMessage = error instanceof Error ? error.message : 'connection error';
|
|
105
|
+
if (errorMessage === '') {
|
|
106
|
+
errorMessage = 'connection error';
|
|
107
|
+
}
|
|
108
|
+
console.error(`Error communicating with the GPT server (${this.adapter.getEndpoint()}):`, errorMessage);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function isNodeError(error) {
|
|
114
|
+
return error instanceof Error && 'code' in error;
|
|
115
|
+
}
|
|
116
|
+
exports.default = ClovingGPT;
|
|
@@ -0,0 +1,47 @@
|
|
|
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 readline_1 = __importDefault(require("readline"));
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const git_utils_1 = require("../utils/git_utils");
|
|
9
|
+
const model_utils_1 = require("../utils/model_utils");
|
|
10
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
11
|
+
const analyze = async () => {
|
|
12
|
+
try {
|
|
13
|
+
// Define the prompt for analysis
|
|
14
|
+
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
15
|
+
const model = (0, model_utils_1.getModel)();
|
|
16
|
+
const prompt = `Explain why the change is being made and document a description of these changes.
|
|
17
|
+
Also list any bugs in the new code as well as recommended fixes for those bugs with code examples.
|
|
18
|
+
|
|
19
|
+
${gitDiff}`;
|
|
20
|
+
// Instantiate ClovingGPT and get the analysis
|
|
21
|
+
const gpt = new cloving_gpt_1.default();
|
|
22
|
+
const analysis = await gpt.generateText({ prompt });
|
|
23
|
+
// Print the analysis to the console
|
|
24
|
+
console.log(analysis);
|
|
25
|
+
// Prompt the user to copy the analysis to the clipboard
|
|
26
|
+
const rl = readline_1.default.createInterface({
|
|
27
|
+
input: process.stdin,
|
|
28
|
+
output: process.stdout
|
|
29
|
+
});
|
|
30
|
+
rl.question('Do you want to copy the analysis to the clipboard? [Y/n] ', (answer) => {
|
|
31
|
+
if (answer.toLowerCase() === 'y' || answer === '') {
|
|
32
|
+
try {
|
|
33
|
+
(0, child_process_1.execFileSync)('pbcopy', { input: analysis });
|
|
34
|
+
console.log('Analysis copied to clipboard');
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error('Error: pbcopy command not found. Unable to copy to clipboard.');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
rl.close();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error('Error during analysis:', error.message);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
exports.default = analyze;
|
|
@@ -0,0 +1,44 @@
|
|
|
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 git_utils_1 = require("../utils/git_utils");
|
|
10
|
+
const string_utils_1 = require("../utils/string_utils");
|
|
11
|
+
const command_utils_1 = require("../utils/command_utils");
|
|
12
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
13
|
+
const generateAndCommitMessage = async (options) => {
|
|
14
|
+
options.silent = (0, command_utils_1.getConfig)().silent || false;
|
|
15
|
+
const gpt = new cloving_gpt_1.default(options);
|
|
16
|
+
try {
|
|
17
|
+
// Generate the prompt for commit message
|
|
18
|
+
const prompt = (0, git_utils_1.generateCommitMessagePrompt)();
|
|
19
|
+
// Instantiate ClovingGPT and get the commit message
|
|
20
|
+
const rawCommitMessage = await gpt.generateText({ prompt });
|
|
21
|
+
// Clean the commit message using extractMarkdown
|
|
22
|
+
const commitMessage = (0, string_utils_1.extractMarkdown)(rawCommitMessage);
|
|
23
|
+
// Write the commit message to a temporary file
|
|
24
|
+
const tempCommitFilePath = path_1.default.join('.git', 'SUGGESTED_COMMIT_EDITMSG');
|
|
25
|
+
fs_1.default.writeFileSync(tempCommitFilePath, commitMessage);
|
|
26
|
+
// Commit the changes using the generated commit message
|
|
27
|
+
try {
|
|
28
|
+
(0, child_process_1.execFileSync)('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], { stdio: 'inherit' });
|
|
29
|
+
}
|
|
30
|
+
catch (commitError) {
|
|
31
|
+
// If commit is canceled (non-zero exit), handle it here
|
|
32
|
+
console.log('Commit was canceled or failed.');
|
|
33
|
+
}
|
|
34
|
+
// Remove the temporary file using fs
|
|
35
|
+
fs_1.default.unlink(tempCommitFilePath, (err) => {
|
|
36
|
+
if (err)
|
|
37
|
+
throw err;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error('Could not generate commit message');
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
exports.default = generateAndCommitMessage;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.config = void 0;
|
|
4
|
+
const command_utils_1 = require("../utils/command_utils");
|
|
5
|
+
const config = async () => {
|
|
6
|
+
let currentConfig = (0, command_utils_1.getConfig)();
|
|
7
|
+
// Ensure models is initialized properly
|
|
8
|
+
if (!currentConfig.models) {
|
|
9
|
+
currentConfig.models = {};
|
|
10
|
+
}
|
|
11
|
+
const models = await (0, command_utils_1.fetchModels)();
|
|
12
|
+
if (models.length === 0) {
|
|
13
|
+
console.error('No models available.');
|
|
14
|
+
throw new Error('No models available');
|
|
15
|
+
}
|
|
16
|
+
let continueConfig = true;
|
|
17
|
+
while (continueConfig) {
|
|
18
|
+
console.log('Available models:');
|
|
19
|
+
models.forEach((model, index) => console.log(`${index + 1}. ${model}`));
|
|
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.');
|
|
23
|
+
throw new Error('Invalid selection');
|
|
24
|
+
}
|
|
25
|
+
const selectedModel = models[modelIndex];
|
|
26
|
+
const apiKey = await (0, command_utils_1.promptUser)(`Enter your API key for ${selectedModel}: `);
|
|
27
|
+
currentConfig.models[selectedModel] = apiKey;
|
|
28
|
+
const setAsPrimary = await (0, command_utils_1.promptUser)('Do you want to set this model as the primary model? [Yn]: ');
|
|
29
|
+
if (setAsPrimary.toLowerCase() === 'y' || setAsPrimary === '') {
|
|
30
|
+
currentConfig.primaryModel = selectedModel;
|
|
31
|
+
}
|
|
32
|
+
const anotherModel = await (0, command_utils_1.promptUser)('Do you want to configure another model? [Yn]: ');
|
|
33
|
+
if (anotherModel.toLowerCase() !== 'y' && anotherModel !== '') {
|
|
34
|
+
continueConfig = false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
(0, command_utils_1.saveConfig)(currentConfig);
|
|
38
|
+
};
|
|
39
|
+
exports.config = config;
|
|
40
|
+
exports.default = exports.config;
|