@gitset-dev/cli 1.0.0 → 1.2.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/README.md +191 -1
- package/package.json +1 -1
- package/src/index.js +371 -97
package/README.md
CHANGED
|
@@ -1 +1,191 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://github.com/imprvhub/gitset/blob/main/public/favicon-192.png" alt="GitSet CLI" />
|
|
3
|
+
<br>
|
|
4
|
+
<a href="https://badge.fury.io/js/@gitset-dev%2Fcli">
|
|
5
|
+
<img src="https://img.shields.io/npm/v/@gitset-dev/cli?color=%237BFEF5" alt="npm version" />
|
|
6
|
+
</a>
|
|
7
|
+
<a href="https://opensource.org/licenses/MPL-2.0">
|
|
8
|
+
<img src="https://img.shields.io/badge/License-MPL_2.0-%237BFEF5" alt="License: MPL 2.0" />
|
|
9
|
+
</a>
|
|
10
|
+
<br>
|
|
11
|
+
<br>
|
|
12
|
+
<h3>
|
|
13
|
+
GitSet CLI - AI-Driven Commit Message Generation
|
|
14
|
+
</h3>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
GitSet CLI is an integral component of the GitSet.dev ecosystem, designed to enhance Git workflow automation through AI-driven commit message generation. By leveraging Google's Gemini Pro AI technology, it provides intelligent analysis of staged changes to generate contextually appropriate commit messages, supporting both semantic and personalized formatting styles.
|
|
20
|
+
|
|
21
|
+
## Key Capabilities
|
|
22
|
+
|
|
23
|
+
The GitSet CLI enhances repository management through:
|
|
24
|
+
|
|
25
|
+
- **AI-Powered Analysis**: Utilizes advanced AI processing to analyze staged changes and generate contextually appropriate commit messages
|
|
26
|
+
- **Semantic Versioning Support**: Implements conventional commit standards for maintaining structured version control
|
|
27
|
+
- **Style Adaptation**: Analyzes existing commit patterns to match personal or team commit message conventions
|
|
28
|
+
- **Efficient Processing**: Provides rapid analysis and suggestion generation while maintaining minimal resource utilization
|
|
29
|
+
- **Cross-Platform Architecture**: Ensures consistent operation across various operating systems and environments
|
|
30
|
+
- **License Management**: Flexible licensing system with both free and pro tiers for different usage needs
|
|
31
|
+
- **Usage Tracking**: Built-in monitoring of API usage and license status
|
|
32
|
+
|
|
33
|
+
## System Requirements
|
|
34
|
+
|
|
35
|
+
- Node.js Runtime Environment (Version 18.0.0 or higher)
|
|
36
|
+
- Git (Installed and configured)
|
|
37
|
+
- Active internet connection for AI processing
|
|
38
|
+
|
|
39
|
+
## Installation Process
|
|
40
|
+
|
|
41
|
+
Install the GitSet CLI globally via npm:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g @gitset-dev/cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Implementation Guide
|
|
48
|
+
|
|
49
|
+
### Basic Usage
|
|
50
|
+
|
|
51
|
+
1. Stage your modifications:
|
|
52
|
+
```bash
|
|
53
|
+
git add .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. Generate commit message suggestions:
|
|
57
|
+
```bash
|
|
58
|
+
# Semantic versioning format (default)
|
|
59
|
+
gitset suggest
|
|
60
|
+
|
|
61
|
+
# Custom formatting style
|
|
62
|
+
gitset suggest --mode custom
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3. Implement the generated message:
|
|
66
|
+
```bash
|
|
67
|
+
git commit -m "generated_message"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### License Management
|
|
71
|
+
|
|
72
|
+
Activate your GitSet license:
|
|
73
|
+
```bash
|
|
74
|
+
# Activate with license key
|
|
75
|
+
gitset activate <license-key>
|
|
76
|
+
|
|
77
|
+
# Check license status and usage
|
|
78
|
+
gitset status
|
|
79
|
+
|
|
80
|
+
# Remove current license
|
|
81
|
+
gitset deactivate
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Operational Modes
|
|
85
|
+
|
|
86
|
+
#### Semantic Mode (Default Implementation)
|
|
87
|
+
Implements conventional commit standards to generate structured, semantic commit messages. This mode is optimized for maintaining consistent and professional Git history in enterprise environments.
|
|
88
|
+
|
|
89
|
+
Example output:
|
|
90
|
+
```bash
|
|
91
|
+
$ gitset suggest
|
|
92
|
+
✨ Generated Suggestion:
|
|
93
|
+
------------------------
|
|
94
|
+
feat: Implement JWT authentication system
|
|
95
|
+
|
|
96
|
+
- Add token generation and validation mechanisms
|
|
97
|
+
- Integrate login and registration endpoints
|
|
98
|
+
- Configure route protection middleware
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Custom Mode
|
|
102
|
+
Analyzes existing commit patterns to generate messages that align with established conventions:
|
|
103
|
+
|
|
104
|
+
- Evaluates recent commit history (default: 20 commits) for pattern recognition
|
|
105
|
+
- Adapts to existing formatting conventions and structural patterns
|
|
106
|
+
- Maintains sequential naming conventions if detected
|
|
107
|
+
- Preserves emoji usage patterns and placement
|
|
108
|
+
- Replicates capitalization and punctuation styles
|
|
109
|
+
- Balances descriptive content with stylistic consistency
|
|
110
|
+
|
|
111
|
+
Example of style adaptation:
|
|
112
|
+
```bash
|
|
113
|
+
# Given existing commit pattern:
|
|
114
|
+
FEATURE_123: Enhanced login interface 🚀
|
|
115
|
+
FEATURE_124: Updated navigation system ✨
|
|
116
|
+
FEATURE_125: Resolved routing conflicts 🔧
|
|
117
|
+
|
|
118
|
+
# Generated suggestion maintains consistency:
|
|
119
|
+
FEATURE_126: Implemented user preferences 🎯
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Configuration Reference
|
|
123
|
+
|
|
124
|
+
### Command Structure
|
|
125
|
+
|
|
126
|
+
Available commands:
|
|
127
|
+
- `gitset suggest` - Initiates commit message generation based on staged changes
|
|
128
|
+
- `gitset activate` - Activates GitSet with a license key
|
|
129
|
+
- `gitset status` - Checks current license status and usage
|
|
130
|
+
- `gitset deactivate` - Removes current license configuration
|
|
131
|
+
- `gitset help` - Displays detailed usage information
|
|
132
|
+
|
|
133
|
+
### Available Parameters
|
|
134
|
+
|
|
135
|
+
For suggest command:
|
|
136
|
+
- `--mode <mode>` - Specifies generation mode ('semantic' or 'custom')
|
|
137
|
+
- `--commit-count <count>` - Defines number of commits to analyze (default: 20)
|
|
138
|
+
- `--version` - Displays CLI version information
|
|
139
|
+
- `--help` - Provides command usage information
|
|
140
|
+
|
|
141
|
+
## Plans and Pricing
|
|
142
|
+
|
|
143
|
+
- **Basic (Free)**
|
|
144
|
+
- 10 requests per month
|
|
145
|
+
- Basic commit message generation
|
|
146
|
+
- Semantic and custom modes support
|
|
147
|
+
|
|
148
|
+
- **Pro**
|
|
149
|
+
- Unlimited requests
|
|
150
|
+
- Advanced features and priority support
|
|
151
|
+
- Visit https://gitset.dev/pricing for details
|
|
152
|
+
|
|
153
|
+
## Development Contribution
|
|
154
|
+
|
|
155
|
+
We welcome contributions to enhance the GitSet CLI. Please follow these steps:
|
|
156
|
+
|
|
157
|
+
1. Fork the repository
|
|
158
|
+
2. Create a feature branch:
|
|
159
|
+
```bash
|
|
160
|
+
git checkout -b feature/enhancement-description
|
|
161
|
+
```
|
|
162
|
+
3. Implement modifications:
|
|
163
|
+
```bash
|
|
164
|
+
git commit -m 'feat: Add enhancement description'
|
|
165
|
+
```
|
|
166
|
+
4. Push changes:
|
|
167
|
+
```bash
|
|
168
|
+
git push origin feature/enhancement-description
|
|
169
|
+
```
|
|
170
|
+
5. Submit a Pull Request
|
|
171
|
+
|
|
172
|
+
## License Information
|
|
173
|
+
|
|
174
|
+
This project operates under the Mozilla Public License 2.0 - refer to [LICENSE.md](LICENSE.md) for detailed terms.
|
|
175
|
+
|
|
176
|
+
## Support Channels
|
|
177
|
+
|
|
178
|
+
- Technical Support: support@gitset.dev
|
|
179
|
+
- Contact Form: https://gitset.dev/contact
|
|
180
|
+
- Issue Tracking: https://github.com/gitset-dev/gitset-cli/issues
|
|
181
|
+
- Account Management: https://gitset.dev/account
|
|
182
|
+
|
|
183
|
+
## Acknowledgments
|
|
184
|
+
|
|
185
|
+
- Contributors who have helped improve this tool
|
|
186
|
+
- Commander.js for CLI framework support
|
|
187
|
+
- Google's Gemini Pro for AI capabilities
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
Part of the [GitSet.dev](https://gitset.dev) ecosystem - Smart AI Documentation & Version Control for GitHub Repositories.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import { exec } from 'child_process';
|
|
3
3
|
import { promisify } from 'util';
|
|
4
4
|
import fetch from 'node-fetch';
|
|
5
5
|
import { program } from 'commander';
|
|
6
6
|
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import crypto from 'crypto';
|
|
7
10
|
|
|
8
11
|
const execAsync = promisify(exec);
|
|
12
|
+
const API_URL = 'https://gitset-commit-messages.vercel.app';
|
|
13
|
+
|
|
14
|
+
const CONFIG_LOCATIONS = {
|
|
15
|
+
global: {
|
|
16
|
+
dir: path.join(os.homedir(), '.config', 'gitset'),
|
|
17
|
+
file: 'credentials.json'
|
|
18
|
+
},
|
|
19
|
+
local: {
|
|
20
|
+
dir: path.join(process.cwd(), '.gitset'),
|
|
21
|
+
file: 'credentials.json'
|
|
22
|
+
},
|
|
23
|
+
system: {
|
|
24
|
+
darwin: path.join(os.homedir(), 'Library', 'Application Support', 'GitSet'),
|
|
25
|
+
win32: path.join(process.env.APPDATA || '', 'GitSet'),
|
|
26
|
+
linux: path.join(os.homedir(), '.local', 'share', 'gitset')
|
|
27
|
+
}
|
|
28
|
+
};
|
|
9
29
|
|
|
10
30
|
const colors = {
|
|
11
31
|
reset: '\x1b[0m',
|
|
@@ -27,60 +47,115 @@ function log(step, message, isError = false) {
|
|
|
27
47
|
console.log(`${prefix} ${colors.reset}[${timestamp}] ${stepColor}${step}${colors.reset}: ${message}`);
|
|
28
48
|
}
|
|
29
49
|
|
|
30
|
-
|
|
50
|
+
function getConfigPath() {
|
|
51
|
+
const localPath = path.join(CONFIG_LOCATIONS.local.dir, CONFIG_LOCATIONS.local.file);
|
|
31
52
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let previousContent = '';
|
|
36
|
-
try {
|
|
37
|
-
const { stdout } = await execAsync(`git show ${lastCommit.trim()}:${file}`);
|
|
38
|
-
const lineCount = stdout.split('\n').length;
|
|
39
|
-
if (lineCount > MAX_LINES) {
|
|
40
|
-
log('Diff', `Skipping ${file}: exceeds ${MAX_LINES} lines (has ${lineCount} lines)`, true);
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
previousContent = stdout;
|
|
44
|
-
log('Diff', `✓ Retrieved previous content (${stdout.length} bytes, ${lineCount} lines)`);
|
|
45
|
-
} catch (e) {
|
|
46
|
-
log('Diff', `New file detected: ${file}`);
|
|
53
|
+
if (fs.existsSync(localPath)) {
|
|
54
|
+
return localPath;
|
|
47
55
|
}
|
|
56
|
+
} catch (error) {}
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
return null;
|
|
58
|
+
const globalPath = path.join(CONFIG_LOCATIONS.global.dir, CONFIG_LOCATIONS.global.file);
|
|
59
|
+
try {
|
|
60
|
+
if (fs.existsSync(globalPath)) {
|
|
61
|
+
return globalPath;
|
|
54
62
|
}
|
|
55
|
-
|
|
63
|
+
} catch (error) {}
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
const systemDir = CONFIG_LOCATIONS.system[process.platform] || CONFIG_LOCATIONS.system.linux;
|
|
66
|
+
return path.join(systemDir, CONFIG_LOCATIONS.local.file);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function ensureConfigDir() {
|
|
70
|
+
const configPath = getConfigPath();
|
|
71
|
+
const configDir = path.dirname(configPath);
|
|
72
|
+
try {
|
|
73
|
+
await fs.mkdir(configDir, { recursive: true, mode: 0o700 });
|
|
61
74
|
} catch (error) {
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
if (error.code !== 'EEXIST') {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
64
78
|
}
|
|
79
|
+
return configPath;
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
async function
|
|
82
|
+
async function saveConfig(config) {
|
|
83
|
+
const configPath = await ensureConfigDir();
|
|
84
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function loadConfig() {
|
|
88
|
+
const storedConfig = {
|
|
89
|
+
licenseKey: null,
|
|
90
|
+
lastValidation: null,
|
|
91
|
+
installationId: null
|
|
92
|
+
};
|
|
93
|
+
|
|
68
94
|
try {
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
const configPath = getConfigPath();
|
|
96
|
+
const configData = await fs.readFile(configPath, 'utf8');
|
|
97
|
+
const parsedConfig = JSON.parse(configData);
|
|
98
|
+
|
|
99
|
+
if (!parsedConfig.installationId) {
|
|
100
|
+
parsedConfig.installationId = crypto.randomUUID();
|
|
101
|
+
await saveConfig(parsedConfig);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return parsedConfig;
|
|
77
105
|
} catch (error) {
|
|
78
|
-
|
|
79
|
-
|
|
106
|
+
if (error.code === 'ENOENT') {
|
|
107
|
+
|
|
108
|
+
storedConfig.installationId = crypto.randomUUID();
|
|
109
|
+
await saveConfig(storedConfig);
|
|
110
|
+
return storedConfig;
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function makeApiRequest(endpoint, data) {
|
|
117
|
+
const config = await loadConfig();
|
|
118
|
+
const headers = {
|
|
119
|
+
'Content-Type': 'application/json'
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (config.licenseKey) {
|
|
123
|
+
headers['X-License-Key'] = config.licenseKey;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!config.installationId) {
|
|
127
|
+
config.installationId = crypto.randomUUID();
|
|
128
|
+
await saveConfig(config);
|
|
80
129
|
}
|
|
130
|
+
|
|
131
|
+
headers['X-Installation-Id'] = config.installationId;
|
|
132
|
+
|
|
133
|
+
const response = await fetch(`${API_URL}${endpoint}`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers,
|
|
136
|
+
body: JSON.stringify(data)
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const errorData = await response.json();
|
|
141
|
+
|
|
142
|
+
if (response.status === 429 && errorData.status === 'quota_exceeded') {
|
|
143
|
+
log('Quota', 'Monthly request limit reached:', true);
|
|
144
|
+
log('Info', errorData.message);
|
|
145
|
+
log('Info', `Next reset date: ${new Date(errorData.next_reset_date).toLocaleDateString()}`);
|
|
146
|
+
log('Info', `Remaining days: ${errorData.days_until_reset}`);
|
|
147
|
+
log('Upgrade', 'To remove limits, activate a pro license:');
|
|
148
|
+
log('Command', ' gitset activate <license-key>');
|
|
149
|
+
log('Purchase', 'Visit https://gitset.dev/pricing to get a license');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new Error(errorData.message || 'Failed to make request');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return response.json();
|
|
81
157
|
}
|
|
82
158
|
|
|
83
|
-
// Files that should typically be ignored
|
|
84
159
|
const IGNORED_FILES = [
|
|
85
160
|
'package-lock.json',
|
|
86
161
|
'yarn.lock',
|
|
@@ -96,8 +171,6 @@ const IGNORED_FILES = [
|
|
|
96
171
|
'coverage'
|
|
97
172
|
];
|
|
98
173
|
|
|
99
|
-
const MAX_LINES = 3000;
|
|
100
|
-
|
|
101
174
|
function shouldIgnoreFile(filePath) {
|
|
102
175
|
return IGNORED_FILES.some(ignored =>
|
|
103
176
|
filePath === ignored ||
|
|
@@ -108,9 +181,49 @@ function shouldIgnoreFile(filePath) {
|
|
|
108
181
|
);
|
|
109
182
|
}
|
|
110
183
|
|
|
184
|
+
async function getLastCommits(count = 20) {
|
|
185
|
+
try {
|
|
186
|
+
log('History', `Fetching last ${count} commits...`);
|
|
187
|
+
const { stdout } = await execAsync(`git log -${count} --pretty=format:"%s"`);
|
|
188
|
+
const commits = stdout.split('\n');
|
|
189
|
+
log('History', `✓ Retrieved ${commits.length} commits`);
|
|
190
|
+
return commits;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
log('History', `Error fetching commit history: ${error.message}`, true);
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function getGitDiff(file) {
|
|
198
|
+
try {
|
|
199
|
+
log('Diff', `Getting differences for ${file}...`);
|
|
200
|
+
const { stdout: lastCommit } = await execAsync('git rev-parse HEAD');
|
|
201
|
+
|
|
202
|
+
let previousContent = '';
|
|
203
|
+
try {
|
|
204
|
+
const { stdout } = await execAsync(`git show ${lastCommit.trim()}:${file}`);
|
|
205
|
+
previousContent = stdout;
|
|
206
|
+
log('Diff', `✓ Retrieved previous content`);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
log('Diff', `New file detected: ${file}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const { stdout: currentContent } = await execAsync(`git show :${file}`);
|
|
212
|
+
log('Diff', `✓ Retrieved current content`);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
previous: previousContent,
|
|
216
|
+
current: currentContent
|
|
217
|
+
};
|
|
218
|
+
} catch (error) {
|
|
219
|
+
log('Diff', `Error processing ${file}: ${error.message}`, true);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
111
224
|
async function getStagedFiles() {
|
|
112
225
|
try {
|
|
113
|
-
log('Git', '
|
|
226
|
+
log('Git', 'Retrieving staged files...');
|
|
114
227
|
const { stdout } = await execAsync('git diff --cached --name-status');
|
|
115
228
|
const files = stdout.split('\n')
|
|
116
229
|
.filter(line => line)
|
|
@@ -129,9 +242,26 @@ async function getStagedFiles() {
|
|
|
129
242
|
}
|
|
130
243
|
}
|
|
131
244
|
|
|
132
|
-
async function
|
|
245
|
+
async function getRepoInfo() {
|
|
246
|
+
try {
|
|
247
|
+
log('Repo', 'Retrieving repository information...');
|
|
248
|
+
const { stdout: remoteUrl } = await execAsync('git config --get remote.origin.url');
|
|
249
|
+
const repoPath = remoteUrl.trim()
|
|
250
|
+
.replace('git@github.com:', '')
|
|
251
|
+
.replace('https://github.com/', '')
|
|
252
|
+
.replace('.git', '');
|
|
253
|
+
log('Repo', `✓ Repository identified: ${repoPath}`);
|
|
254
|
+
return repoPath;
|
|
255
|
+
} catch (error) {
|
|
256
|
+
log('Repo', `Error: ${error.message}`, true);
|
|
257
|
+
return '';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function generateCommitMessage(options) {
|
|
133
262
|
try {
|
|
134
263
|
log('Start', 'Starting commit message generation process...');
|
|
264
|
+
const { mode = 'semantic', commitCount = 20 } = options;
|
|
135
265
|
|
|
136
266
|
const files = await getStagedFiles();
|
|
137
267
|
if (files.length === 0) {
|
|
@@ -149,71 +279,52 @@ async function generateCommitMessage() {
|
|
|
149
279
|
console.log(` ${statusText}${colors.reset}: ${file}`);
|
|
150
280
|
});
|
|
151
281
|
|
|
152
|
-
const fileChanges =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const { previous, current } = diffResult;
|
|
158
|
-
const extension = path.extname(file).toLowerCase();
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
name: path.basename(file),
|
|
162
|
-
path: file,
|
|
163
|
-
changeType: status === 'A' ? 'added' : status === 'M' ? 'modified' : 'deleted',
|
|
164
|
-
contentType: ['.jpg', '.png', '.gif', '.pdf'].includes(extension) ? 'binary' : 'text',
|
|
165
|
-
changes: {
|
|
166
|
-
before: previous,
|
|
167
|
-
after: current
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
})
|
|
171
|
-
)).filter(change => change !== null);
|
|
172
|
-
|
|
173
|
-
const repoName = await getRepoInfo();
|
|
174
|
-
log('API', 'Processing diffs with AI...');
|
|
175
|
-
log('NOTE', `The following commit message is a general reference and may not be fully accurate. If it doesn't meet your expectations, try again or suggest improvements here: https://gitset.dev/contact`)
|
|
176
|
-
const response = await fetch('https://gitset-commit-messages.vercel.app/generate-commit-message', {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
headers: { 'Content-Type': 'application/json' },
|
|
179
|
-
body: JSON.stringify({
|
|
180
|
-
repo_name: repoName,
|
|
181
|
-
file_changes: fileChanges
|
|
182
|
-
})
|
|
183
|
-
});
|
|
282
|
+
const fileChanges = [];
|
|
283
|
+
for (const { status, file } of files) {
|
|
284
|
+
const diffResult = await getGitDiff(file);
|
|
285
|
+
if (!diffResult) continue;
|
|
184
286
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
} else {
|
|
194
|
-
errorMessage = `Server error (${response.status}): ${parsedError.message || errorData}`;
|
|
287
|
+
fileChanges.push({
|
|
288
|
+
name: path.basename(file),
|
|
289
|
+
path: file,
|
|
290
|
+
changeType: status === 'A' ? 'added' : status === 'M' ? 'modified' : 'deleted',
|
|
291
|
+
contentType: 'text',
|
|
292
|
+
changes: {
|
|
293
|
+
before: diffResult.previous,
|
|
294
|
+
after: diffResult.current
|
|
195
295
|
}
|
|
196
|
-
}
|
|
197
|
-
errorMessage = `Server error (${response.status}): ${errorData}`;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
throw new Error(errorMessage);
|
|
296
|
+
});
|
|
201
297
|
}
|
|
202
298
|
|
|
203
|
-
const
|
|
299
|
+
const repoName = await getRepoInfo();
|
|
300
|
+
const commitHistory = mode === 'custom' ? await getLastCommits(parseInt(commitCount)) : [];
|
|
301
|
+
|
|
302
|
+
log('API', `Processing diffs with AI using ${mode} mode...`);
|
|
303
|
+
|
|
304
|
+
const response = await makeApiRequest('/generate-commit-message', {
|
|
305
|
+
repo_name: repoName,
|
|
306
|
+
file_changes: fileChanges,
|
|
307
|
+
mode: mode,
|
|
308
|
+
commit_history: commitHistory
|
|
309
|
+
});
|
|
310
|
+
|
|
204
311
|
log('Success', '✨ Commit message generated\n');
|
|
312
|
+
|
|
205
313
|
function formatCommitMessage(message) {
|
|
206
314
|
const [title, ...descriptionParts] = message.split('\n');
|
|
207
315
|
const description = descriptionParts.join('\n');
|
|
208
316
|
return `${colors.bright}${colors.customCyan}${title}${colors.reset}${description ? `\n${colors.darkCyan}${description}${colors.reset}` : ''}`;
|
|
209
317
|
}
|
|
210
318
|
|
|
211
|
-
console.log(`${colors.bright}📝 Suggested message:${colors.reset}`);
|
|
319
|
+
console.log(`${colors.bright}📝 Suggested message (${mode} mode):${colors.reset}`);
|
|
212
320
|
console.log(`${colors.yellow}------------------${colors.reset}`);
|
|
213
|
-
console.log(formatCommitMessage(commit_message));
|
|
321
|
+
console.log(formatCommitMessage(response.commit_message));
|
|
214
322
|
|
|
215
323
|
} catch (error) {
|
|
216
324
|
log('Error', error.message, true);
|
|
325
|
+
if (error.message.includes('permission denied')) {
|
|
326
|
+
log('Help', 'Make sure you have the necessary permissions and are in a git repository.');
|
|
327
|
+
}
|
|
217
328
|
process.exit(1);
|
|
218
329
|
}
|
|
219
330
|
}
|
|
@@ -221,11 +332,174 @@ async function generateCommitMessage() {
|
|
|
221
332
|
program
|
|
222
333
|
.name('gitset')
|
|
223
334
|
.description('Smart AI Docs & Versioning for GitHub Repositories.')
|
|
224
|
-
.version('1.
|
|
335
|
+
.version('1.1.0');
|
|
225
336
|
|
|
226
337
|
program
|
|
338
|
+
.command('activate')
|
|
339
|
+
.description('Activate GitSet with a license key')
|
|
340
|
+
.argument('[licenseKey]', 'Your GitSet license key')
|
|
341
|
+
.action(async (licenseKey) => {
|
|
342
|
+
try {
|
|
343
|
+
if (!licenseKey) {
|
|
344
|
+
const config = await loadConfig();
|
|
345
|
+
if (config.licenseKey) {
|
|
346
|
+
log('License', 'Current license status:');
|
|
347
|
+
const status = await makeApiRequest('/validate-license', { licenseKey: config.licenseKey });
|
|
348
|
+
log('Status', `Plan: ${status.data.product_name}`);
|
|
349
|
+
log('Status', `Valid until: ${new Date(status.data.renews_at).toLocaleDateString()}`);
|
|
350
|
+
} else {
|
|
351
|
+
log('License', 'No license key configured', true);
|
|
352
|
+
log('Info', 'You can use GitSet with limited features (10 requests/month)');
|
|
353
|
+
log('Info', 'To unlock unlimited usage, activate a pro license:');
|
|
354
|
+
log('Command', ' gitset activate <license-key>');
|
|
355
|
+
log('Purchase', 'Visit https://gitset.dev/pricing to get a license');
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const response = await makeApiRequest('/validate-license', { licenseKey });
|
|
361
|
+
const config = await loadConfig();
|
|
362
|
+
await saveConfig({
|
|
363
|
+
...config, // Mantiene el installationId existente
|
|
364
|
+
licenseKey,
|
|
365
|
+
lastValidation: new Date().toISOString(),
|
|
366
|
+
subscriptionData: response.data
|
|
367
|
+
});
|
|
368
|
+
log('Success', '✨ License activated successfully!');
|
|
369
|
+
log('Plan', response.data.product_name);
|
|
370
|
+
log('Info', 'You now have unlimited access to all features');
|
|
371
|
+
|
|
372
|
+
} catch (error) {
|
|
373
|
+
log('Error', error.message, true);
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
program
|
|
227
379
|
.command('suggest')
|
|
228
|
-
.description('Generate
|
|
380
|
+
.description('Generate commit messages using AI-driven analysis of staged code changes.')
|
|
381
|
+
.option('-m, --mode <mode>', 'Commit message mode (semantic or custom)', 'semantic')
|
|
382
|
+
.option('-c, --commit-count <count>', 'Number of previous commits to analyze for custom mode', '20')
|
|
229
383
|
.action(generateCommitMessage);
|
|
230
384
|
|
|
385
|
+
program
|
|
386
|
+
.command('status')
|
|
387
|
+
.description('Check your GitSet license status and usage')
|
|
388
|
+
.action(async () => {
|
|
389
|
+
try {
|
|
390
|
+
const config = await loadConfig();
|
|
391
|
+
|
|
392
|
+
if (!config.licenseKey) {
|
|
393
|
+
log('License', 'No license key configured');
|
|
394
|
+
log('Info', 'You are using GitSet with limited features (10 requests/month)');
|
|
395
|
+
log('Info', 'To unlock unlimited usage, activate a pro license:');
|
|
396
|
+
log('Command', ' gitset activate <license-key>');
|
|
397
|
+
log('Purchase', 'Visit https://gitset.dev/pricing to get a license');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const response = await makeApiRequest('/validate-license', { licenseKey: config.licenseKey });
|
|
402
|
+
|
|
403
|
+
log('License', 'License is active and valid ✨');
|
|
404
|
+
log('Plan', response.data.product_name);
|
|
405
|
+
log('Status', response.data.status);
|
|
406
|
+
if (response.data.renews_at) {
|
|
407
|
+
log('Renewal', `Next renewal: ${new Date(response.data.renews_at).toLocaleDateString()}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!response.data.product_name.toLowerCase().includes('pro')) {
|
|
411
|
+
log('Usage', 'Request limits (Basic Plan):');
|
|
412
|
+
const usageResponse = await makeApiRequest('/usage-info', { licenseKey: config.licenseKey });
|
|
413
|
+
log('Info', `Used ${usageResponse.current_usage} of ${usageResponse.limit} monthly requests`);
|
|
414
|
+
log('Info', `Reset date: ${new Date(usageResponse.next_reset_date).toLocaleDateString()}`);
|
|
415
|
+
} else {
|
|
416
|
+
log('Usage', 'Unlimited requests (Pro Plan)');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
} catch (error) {
|
|
420
|
+
log('Error', error.message, true);
|
|
421
|
+
if (error.message.includes('validation')) {
|
|
422
|
+
log('Help', 'Your license might have expired or been deactivated');
|
|
423
|
+
log('Info', 'You can check your subscription status at https://gitset.dev/account');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
program
|
|
429
|
+
.command('deactivate')
|
|
430
|
+
.description('Deactivate and remove current license key')
|
|
431
|
+
.action(async () => {
|
|
432
|
+
try {
|
|
433
|
+
const config = await loadConfig();
|
|
434
|
+
|
|
435
|
+
if (!config.licenseKey) {
|
|
436
|
+
log('Info', 'No active license found');
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
await saveConfig({
|
|
441
|
+
...config,
|
|
442
|
+
licenseKey: null,
|
|
443
|
+
lastValidation: null,
|
|
444
|
+
subscriptionData: null,
|
|
445
|
+
});
|
|
446
|
+
log('Success', 'License deactivated successfully');
|
|
447
|
+
log('Info', 'Switched to basic plan (10 requests/month)');
|
|
448
|
+
log('Info', 'You can reactivate your license anytime with:');
|
|
449
|
+
log('Command', ' gitset activate <license-key>');
|
|
450
|
+
|
|
451
|
+
} catch (error) {
|
|
452
|
+
log('Error', error.message, true);
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
program
|
|
458
|
+
.command('help')
|
|
459
|
+
.description('Show detailed help and usage information')
|
|
460
|
+
.action(() => {
|
|
461
|
+
console.log(`
|
|
462
|
+
${colors.bright}GitSet CLI - Smart AI Commit Messages${colors.reset}
|
|
463
|
+
|
|
464
|
+
${colors.cyan}Usage:${colors.reset}
|
|
465
|
+
gitset suggest Generate a commit message for staged changes
|
|
466
|
+
gitset activate <key> Activate GitSet with a license key
|
|
467
|
+
gitset status Check license status and usage
|
|
468
|
+
gitset deactivate Remove current license key
|
|
469
|
+
gitset help Show this help message
|
|
470
|
+
|
|
471
|
+
${colors.cyan}Options for 'suggest':${colors.reset}
|
|
472
|
+
-m, --mode <mode> Commit message mode (semantic or custom)
|
|
473
|
+
-c, --commit-count <n> Number of commits to analyze in custom mode
|
|
474
|
+
|
|
475
|
+
${colors.cyan}Plans:${colors.reset}
|
|
476
|
+
Basic (Free) 10 requests per month
|
|
477
|
+
Pro Unlimited requests
|
|
478
|
+
|
|
479
|
+
${colors.cyan}Examples:${colors.reset}
|
|
480
|
+
$ gitset suggest Generate semantic commit message
|
|
481
|
+
$ gitset suggest -m custom Generate message matching your style
|
|
482
|
+
$ gitset activate ABC123... Activate pro license
|
|
483
|
+
$ gitset status Check current license status
|
|
484
|
+
|
|
485
|
+
${colors.cyan}Learn more:${colors.reset}
|
|
486
|
+
Visit https://gitset.dev for documentation and pricing
|
|
487
|
+
Report issues at https://github.com/gitset/cli/issues
|
|
488
|
+
`);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
program.exitOverride((err) => {
|
|
492
|
+
if (err.code === 'commander.help') {
|
|
493
|
+
process.exit(0);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
log('Error', err.message, true);
|
|
497
|
+
|
|
498
|
+
if (err.code === 'commander.unknownCommand') {
|
|
499
|
+
log('Help', 'Run "gitset help" to see available commands');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
process.exit(1);
|
|
503
|
+
});
|
|
504
|
+
|
|
231
505
|
program.parse();
|