@gitset-dev/cli 1.1.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 +128 -95
- package/package.json +1 -1
- package/src/index.js +358 -105
package/README.md
CHANGED
|
@@ -1,158 +1,191 @@
|
|
|
1
|
-
# @gitset-dev/cli
|
|
2
|
-
|
|
3
1
|
<div align="center">
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
15
|
</div>
|
|
16
16
|
|
|
17
|
-
##
|
|
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
|
|
18
32
|
|
|
19
|
-
|
|
20
|
-
- 📝 Semantic commit message formatting
|
|
21
|
-
- 🎨 Personal style adaptation (New!)
|
|
22
|
-
- 🔍 Smart analysis of staged changes
|
|
23
|
-
- 🚀 Fast and lightweight
|
|
24
|
-
- 💻 Cross-platform support
|
|
33
|
+
## System Requirements
|
|
25
34
|
|
|
26
|
-
|
|
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:
|
|
27
42
|
|
|
28
43
|
```bash
|
|
29
44
|
npm install -g @gitset-dev/cli
|
|
30
45
|
```
|
|
31
46
|
|
|
32
|
-
##
|
|
47
|
+
## Implementation Guide
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
### Basic Usage
|
|
50
|
+
|
|
51
|
+
1. Stage your modifications:
|
|
35
52
|
```bash
|
|
36
53
|
git add .
|
|
37
54
|
```
|
|
38
55
|
|
|
39
|
-
2. Generate
|
|
56
|
+
2. Generate commit message suggestions:
|
|
40
57
|
```bash
|
|
41
|
-
# Semantic
|
|
58
|
+
# Semantic versioning format (default)
|
|
42
59
|
gitset suggest
|
|
43
60
|
|
|
44
|
-
# Custom style
|
|
61
|
+
# Custom formatting style
|
|
45
62
|
gitset suggest --mode custom
|
|
46
63
|
```
|
|
47
64
|
|
|
48
|
-
3.
|
|
65
|
+
3. Implement the generated message:
|
|
49
66
|
```bash
|
|
50
|
-
git commit -m "
|
|
67
|
+
git commit -m "generated_message"
|
|
51
68
|
```
|
|
52
69
|
|
|
53
|
-
|
|
70
|
+
### License Management
|
|
54
71
|
|
|
72
|
+
Activate your GitSet license:
|
|
55
73
|
```bash
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- Create middleware for route protection
|
|
65
|
-
|
|
66
|
-
# Custom style mode
|
|
67
|
-
$ gitset suggest --mode custom
|
|
68
|
-
✨ Suggested message:
|
|
69
|
-
------------------
|
|
70
|
-
[Auth] Added JWT user authentication 🔐
|
|
71
|
-
|
|
72
|
-
Implemented token system, added login/signup routes,
|
|
73
|
-
and set up protection middleware! Ready for testing.
|
|
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
|
|
74
82
|
```
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
### Operational Modes
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
- Add token generation and validation mechanisms
|
|
97
|
+
- Integrate login and registration endpoints
|
|
98
|
+
- Configure route protection middleware
|
|
99
|
+
```
|
|
82
100
|
|
|
83
|
-
|
|
84
|
-
|
|
101
|
+
#### Custom Mode
|
|
102
|
+
Analyzes existing commit patterns to generate messages that align with established conventions:
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
- Preserves your capitalization and punctuation preferences
|
|
93
|
-
- Keeps descriptive content while matching your style
|
|
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
|
|
94
110
|
|
|
95
111
|
Example of style adaptation:
|
|
96
112
|
```bash
|
|
97
|
-
#
|
|
98
|
-
FEATURE_123:
|
|
99
|
-
FEATURE_124:
|
|
100
|
-
FEATURE_125:
|
|
113
|
+
# Given existing commit pattern:
|
|
114
|
+
FEATURE_123: Enhanced login interface 🚀
|
|
115
|
+
FEATURE_124: Updated navigation system ✨
|
|
116
|
+
FEATURE_125: Resolved routing conflicts 🔧
|
|
101
117
|
|
|
102
|
-
#
|
|
103
|
-
FEATURE_126:
|
|
118
|
+
# Generated suggestion maintains consistency:
|
|
119
|
+
FEATURE_126: Implemented user preferences 🎯
|
|
104
120
|
```
|
|
105
121
|
|
|
106
|
-
## Configuration
|
|
122
|
+
## Configuration Reference
|
|
107
123
|
|
|
108
|
-
|
|
124
|
+
### Command Structure
|
|
109
125
|
|
|
110
|
-
|
|
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
|
|
111
132
|
|
|
112
|
-
###
|
|
133
|
+
### Available Parameters
|
|
113
134
|
|
|
114
|
-
|
|
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
|
|
115
140
|
|
|
116
|
-
|
|
141
|
+
## Plans and Pricing
|
|
117
142
|
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
143
|
+
- **Basic (Free)**
|
|
144
|
+
- 10 requests per month
|
|
145
|
+
- Basic commit message generation
|
|
146
|
+
- Semantic and custom modes support
|
|
122
147
|
|
|
123
|
-
|
|
148
|
+
- **Pro**
|
|
149
|
+
- Unlimited requests
|
|
150
|
+
- Advanced features and priority support
|
|
151
|
+
- Visit https://gitset.dev/pricing for details
|
|
124
152
|
|
|
125
|
-
|
|
153
|
+
## Development Contribution
|
|
126
154
|
|
|
127
|
-
|
|
128
|
-
|
|
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:
|
|
129
159
|
```bash
|
|
130
|
-
git checkout -b feature/
|
|
160
|
+
git checkout -b feature/enhancement-description
|
|
131
161
|
```
|
|
132
|
-
3.
|
|
162
|
+
3. Implement modifications:
|
|
133
163
|
```bash
|
|
134
|
-
git commit -m 'feat: Add
|
|
164
|
+
git commit -m 'feat: Add enhancement description'
|
|
135
165
|
```
|
|
136
|
-
4. Push
|
|
166
|
+
4. Push changes:
|
|
137
167
|
```bash
|
|
138
|
-
git push origin feature/
|
|
168
|
+
git push origin feature/enhancement-description
|
|
139
169
|
```
|
|
140
|
-
5.
|
|
170
|
+
5. Submit a Pull Request
|
|
141
171
|
|
|
142
|
-
## License
|
|
172
|
+
## License Information
|
|
143
173
|
|
|
144
|
-
This project
|
|
174
|
+
This project operates under the Mozilla Public License 2.0 - refer to [LICENSE.md](LICENSE.md) for detailed terms.
|
|
145
175
|
|
|
146
|
-
## Support
|
|
176
|
+
## Support Channels
|
|
147
177
|
|
|
148
|
-
-
|
|
178
|
+
- Technical Support: support@gitset.dev
|
|
149
179
|
- Contact Form: https://gitset.dev/contact
|
|
150
|
-
-
|
|
180
|
+
- Issue Tracking: https://github.com/gitset-dev/gitset-cli/issues
|
|
181
|
+
- Account Management: https://gitset.dev/account
|
|
151
182
|
|
|
152
183
|
## Acknowledgments
|
|
153
184
|
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
185
|
+
- Contributors who have helped improve this tool
|
|
186
|
+
- Commander.js for CLI framework support
|
|
187
|
+
- Google's Gemini Pro for AI capabilities
|
|
157
188
|
|
|
158
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,70 +47,113 @@ 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
|
-
|
|
36
|
-
|
|
53
|
+
if (fs.existsSync(localPath)) {
|
|
54
|
+
return localPath;
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {}
|
|
57
|
+
|
|
58
|
+
const globalPath = path.join(CONFIG_LOCATIONS.global.dir, CONFIG_LOCATIONS.global.file);
|
|
59
|
+
try {
|
|
60
|
+
if (fs.existsSync(globalPath)) {
|
|
61
|
+
return globalPath;
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {}
|
|
64
|
+
|
|
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 });
|
|
37
74
|
} catch (error) {
|
|
38
|
-
|
|
39
|
-
|
|
75
|
+
if (error.code !== 'EEXIST') {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
40
78
|
}
|
|
79
|
+
return configPath;
|
|
41
80
|
}
|
|
42
81
|
|
|
43
|
-
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
|
+
|
|
44
94
|
try {
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
let previousContent = '';
|
|
49
|
-
try {
|
|
50
|
-
const { stdout } = await execAsync(`git show ${lastCommit.trim()}:${file}`);
|
|
51
|
-
const lineCount = stdout.split('\n').length;
|
|
52
|
-
if (lineCount > MAX_LINES) {
|
|
53
|
-
log('Diff', `Skipping ${file}: exceeds ${MAX_LINES} lines (has ${lineCount} lines)`, true);
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
previousContent = stdout;
|
|
57
|
-
log('Diff', `✓ Retrieved previous content (${stdout.length} bytes, ${lineCount} lines)`);
|
|
58
|
-
} catch (e) {
|
|
59
|
-
log('Diff', `New file detected: ${file}`);
|
|
60
|
-
}
|
|
95
|
+
const configPath = getConfigPath();
|
|
96
|
+
const configData = await fs.readFile(configPath, 'utf8');
|
|
97
|
+
const parsedConfig = JSON.parse(configData);
|
|
61
98
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
log('Diff', `Skipping ${file}: exceeds ${MAX_LINES} lines (has ${currentLineCount} lines)`, true);
|
|
66
|
-
return null;
|
|
99
|
+
if (!parsedConfig.installationId) {
|
|
100
|
+
parsedConfig.installationId = crypto.randomUUID();
|
|
101
|
+
await saveConfig(parsedConfig);
|
|
67
102
|
}
|
|
68
|
-
log('Diff', `✓ Retrieved current content (${currentContent.length} bytes, ${currentLineCount} lines)`);
|
|
69
103
|
|
|
70
|
-
return
|
|
71
|
-
previous: previousContent,
|
|
72
|
-
current: currentContent
|
|
73
|
-
};
|
|
104
|
+
return parsedConfig;
|
|
74
105
|
} catch (error) {
|
|
75
|
-
|
|
76
|
-
|
|
106
|
+
if (error.code === 'ENOENT') {
|
|
107
|
+
|
|
108
|
+
storedConfig.installationId = crypto.randomUUID();
|
|
109
|
+
await saveConfig(storedConfig);
|
|
110
|
+
return storedConfig;
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
77
113
|
}
|
|
78
114
|
}
|
|
79
115
|
|
|
80
|
-
async function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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);
|
|
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');
|
|
93
154
|
}
|
|
155
|
+
|
|
156
|
+
return response.json();
|
|
94
157
|
}
|
|
95
158
|
|
|
96
159
|
const IGNORED_FILES = [
|
|
@@ -108,8 +171,6 @@ const IGNORED_FILES = [
|
|
|
108
171
|
'coverage'
|
|
109
172
|
];
|
|
110
173
|
|
|
111
|
-
const MAX_LINES = 3000;
|
|
112
|
-
|
|
113
174
|
function shouldIgnoreFile(filePath) {
|
|
114
175
|
return IGNORED_FILES.some(ignored =>
|
|
115
176
|
filePath === ignored ||
|
|
@@ -120,9 +181,49 @@ function shouldIgnoreFile(filePath) {
|
|
|
120
181
|
);
|
|
121
182
|
}
|
|
122
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
|
+
|
|
123
224
|
async function getStagedFiles() {
|
|
124
225
|
try {
|
|
125
|
-
log('Git', '
|
|
226
|
+
log('Git', 'Retrieving staged files...');
|
|
126
227
|
const { stdout } = await execAsync('git diff --cached --name-status');
|
|
127
228
|
const files = stdout.split('\n')
|
|
128
229
|
.filter(line => line)
|
|
@@ -141,6 +242,22 @@ async function getStagedFiles() {
|
|
|
141
242
|
}
|
|
142
243
|
}
|
|
143
244
|
|
|
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
|
+
|
|
144
261
|
async function generateCommitMessage(options) {
|
|
145
262
|
try {
|
|
146
263
|
log('Start', 'Starting commit message generation process...');
|
|
@@ -162,65 +279,37 @@ async function generateCommitMessage(options) {
|
|
|
162
279
|
console.log(` ${statusText}${colors.reset}: ${file}`);
|
|
163
280
|
});
|
|
164
281
|
|
|
165
|
-
const fileChanges =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const { previous, current } = diffResult;
|
|
171
|
-
const extension = path.extname(file).toLowerCase();
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
name: path.basename(file),
|
|
175
|
-
path: file,
|
|
176
|
-
changeType: status === 'A' ? 'added' : status === 'M' ? 'modified' : 'deleted',
|
|
177
|
-
contentType: ['.jpg', '.png', '.gif', '.pdf'].includes(extension) ? 'binary' : 'text',
|
|
178
|
-
changes: {
|
|
179
|
-
before: previous,
|
|
180
|
-
after: current
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
})
|
|
184
|
-
)).filter(change => change !== null);
|
|
282
|
+
const fileChanges = [];
|
|
283
|
+
for (const { status, file } of files) {
|
|
284
|
+
const diffResult = await getGitDiff(file);
|
|
285
|
+
if (!diffResult) continue;
|
|
185
286
|
|
|
186
|
-
|
|
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
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
187
298
|
|
|
299
|
+
const repoName = await getRepoInfo();
|
|
188
300
|
const commitHistory = mode === 'custom' ? await getLastCommits(parseInt(commitCount)) : [];
|
|
189
301
|
|
|
190
302
|
log('API', `Processing diffs with AI using ${mode} mode...`);
|
|
191
|
-
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`)
|
|
192
303
|
|
|
193
|
-
const response = await
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
file_changes: fileChanges,
|
|
199
|
-
mode: mode,
|
|
200
|
-
commit_history: commitHistory
|
|
201
|
-
})
|
|
304
|
+
const response = await makeApiRequest('/generate-commit-message', {
|
|
305
|
+
repo_name: repoName,
|
|
306
|
+
file_changes: fileChanges,
|
|
307
|
+
mode: mode,
|
|
308
|
+
commit_history: commitHistory
|
|
202
309
|
});
|
|
203
310
|
|
|
204
|
-
if (!response.ok) {
|
|
205
|
-
const errorData = await response.text();
|
|
206
|
-
let errorMessage;
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const parsedError = JSON.parse(errorData);
|
|
210
|
-
if (response.status === 500 && parsedError.message?.includes('429')) {
|
|
211
|
-
errorMessage = `${colors.yellow}The API rate limit has been reached. Please try again in a few minutes.${colors.reset}`;
|
|
212
|
-
} else {
|
|
213
|
-
errorMessage = `Server error (${response.status}): ${parsedError.message || errorData}`;
|
|
214
|
-
}
|
|
215
|
-
} catch {
|
|
216
|
-
errorMessage = `Server error (${response.status}): ${errorData}`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
throw new Error(errorMessage);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const { commit_message } = await response.json();
|
|
223
311
|
log('Success', '✨ Commit message generated\n');
|
|
312
|
+
|
|
224
313
|
function formatCommitMessage(message) {
|
|
225
314
|
const [title, ...descriptionParts] = message.split('\n');
|
|
226
315
|
const description = descriptionParts.join('\n');
|
|
@@ -229,10 +318,13 @@ async function generateCommitMessage(options) {
|
|
|
229
318
|
|
|
230
319
|
console.log(`${colors.bright}📝 Suggested message (${mode} mode):${colors.reset}`);
|
|
231
320
|
console.log(`${colors.yellow}------------------${colors.reset}`);
|
|
232
|
-
console.log(formatCommitMessage(commit_message));
|
|
321
|
+
console.log(formatCommitMessage(response.commit_message));
|
|
233
322
|
|
|
234
323
|
} catch (error) {
|
|
235
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
|
+
}
|
|
236
328
|
process.exit(1);
|
|
237
329
|
}
|
|
238
330
|
}
|
|
@@ -243,10 +335,171 @@ program
|
|
|
243
335
|
.version('1.1.0');
|
|
244
336
|
|
|
245
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
|
|
246
379
|
.command('suggest')
|
|
247
380
|
.description('Generate commit messages using AI-driven analysis of staged code changes.')
|
|
248
381
|
.option('-m, --mode <mode>', 'Commit message mode (semantic or custom)', 'semantic')
|
|
249
382
|
.option('-c, --commit-count <count>', 'Number of previous commits to analyze for custom mode', '20')
|
|
250
383
|
.action(generateCommitMessage);
|
|
251
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
|
+
|
|
252
505
|
program.parse();
|