@gitset-dev/cli 1.1.0 → 1.2.1

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.
Files changed (3) hide show
  1. package/README.md +128 -95
  2. package/package.json +1 -1
  3. 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
- <img src="https://github.com/imprvhub/gitset/blob/main/public/favicon-192.png" alt="Gitset" />
5
- <br>
6
- <a href="https://badge.fury.io/js/@gitset-dev%2Fcli">
7
- <img src="https://img.shields.io/npm/v/@gitset-dev/cli?color=%237BFEF5" alt="npm version" />
8
- </a>
9
- <a href="https://opensource.org/licenses/MPL-2.0">
10
- <img src="https://img.shields.io/badge/License-MPL_2.0-%237BFEF5" alt="License: MPL 2.0" />
11
- </a>
12
- <br>
13
- <br>
14
- <p><em>Generate commit messages using AI-driven analysis - now with style adaptation!</em></p>
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
- ## Features
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
- - 🤖 AI-powered commit message generation
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
- ## Installation
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
- ## Usage
47
+ ## Implementation Guide
33
48
 
34
- 1. Stage your changes:
49
+ ### Basic Usage
50
+
51
+ 1. Stage your modifications:
35
52
  ```bash
36
53
  git add .
37
54
  ```
38
55
 
39
- 2. Generate a commit message:
56
+ 2. Generate commit message suggestions:
40
57
  ```bash
41
- # Semantic mode (default)
58
+ # Semantic versioning format (default)
42
59
  gitset suggest
43
60
 
44
- # Custom style mode
61
+ # Custom formatting style
45
62
  gitset suggest --mode custom
46
63
  ```
47
64
 
48
- 3. Review and use the generated message:
65
+ 3. Implement the generated message:
49
66
  ```bash
50
- git commit -m "your generated message"
67
+ git commit -m "generated_message"
51
68
  ```
52
69
 
53
- ## Examples
70
+ ### License Management
54
71
 
72
+ Activate your GitSet license:
55
73
  ```bash
56
- # Semantic mode (default)
57
- $ gitset suggest
58
- ✨ Suggested message:
59
- ------------------
60
- feat: Add user authentication feature with JWT support
61
-
62
- - Implement JWT token generation and validation
63
- - Add login and signup endpoints
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
- ## Requirements
84
+ ### Operational Modes
77
85
 
78
- - Node.js >= 18.0.0
79
- - Git installed and configured
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
- ## Commit Message Modes
96
+ - Add token generation and validation mechanisms
97
+ - Integrate login and registration endpoints
98
+ - Configure route protection middleware
99
+ ```
82
100
 
83
- ### Semantic Mode (Default)
84
- The default mode follows conventional commit standards to generate structured, semantic commit messages. Perfect for maintaining a clean and standard Git history in professional projects.
101
+ #### Custom Mode
102
+ Analyzes existing commit patterns to generate messages that align with established conventions:
85
103
 
86
- ### Custom Mode
87
- Custom mode analyzes your previous commit messages and adapts to your personal writing style:
88
- - Studies your last commits (default: 20) to understand your patterns
89
- - Learns from your formatting, tone, and structure
90
- - Maintains your sequential patterns if you use them
91
- - Adapts emoji usage based on your style
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
- # If your commits look like this:
98
- FEATURE_123: added login page 🚀
99
- FEATURE_124: updated navbar design
100
- FEATURE_125: fixed routing issues 🔧
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
- # Custom mode will generate similar style:
103
- FEATURE_126: implemented user settings 🎯
118
+ # Generated suggestion maintains consistency:
119
+ FEATURE_126: Implemented user preferences 🎯
104
120
  ```
105
121
 
106
- ## Configuration
122
+ ## Configuration Reference
107
123
 
108
- No additional configuration needed. The CLI automatically detects your Git repository and staged changes.
124
+ ### Command Structure
109
125
 
110
- ## API Reference
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
- ### Commands
133
+ ### Available Parameters
113
134
 
114
- - `gitset suggest` - Generate a commit message based on staged changes
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
- ### Options
141
+ ## Plans and Pricing
117
142
 
118
- - `--mode <mode>` - Choose between 'semantic' (default) or 'custom' style
119
- - `--commit-count <count>` - Number of previous commits to analyze (default: 20)
120
- - `--version` - Show CLI version
121
- - `--help` - Show help information
143
+ - **Basic (Free)**
144
+ - 10 requests per month
145
+ - Basic commit message generation
146
+ - Semantic and custom modes support
122
147
 
123
- ## Contributing
148
+ - **Pro**
149
+ - Unlimited requests
150
+ - Advanced features and priority support
151
+ - Visit https://gitset.dev/pricing for details
124
152
 
125
- We love your input! We want to make contributing to @gitset-dev/cli as easy and transparent as possible.
153
+ ## Development Contribution
126
154
 
127
- 1. Fork the repo
128
- 2. Create your feature branch:
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/amazing-feature
160
+ git checkout -b feature/enhancement-description
131
161
  ```
132
- 3. Commit your changes:
162
+ 3. Implement modifications:
133
163
  ```bash
134
- git commit -m 'feat: Add some amazing feature'
164
+ git commit -m 'feat: Add enhancement description'
135
165
  ```
136
- 4. Push to the branch:
166
+ 4. Push changes:
137
167
  ```bash
138
- git push origin feature/amazing-feature
168
+ git push origin feature/enhancement-description
139
169
  ```
140
- 5. Open a Pull Request
170
+ 5. Submit a Pull Request
141
171
 
142
- ## License
172
+ ## License Information
143
173
 
144
- This project is licensed under the Mozilla Public License 2.0 - see the [LICENSE.md](LICENSE.md) file for details.
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
- - Email: support@gitset.dev
178
+ - Technical Support: support@gitset.dev
149
179
  - Contact Form: https://gitset.dev/contact
150
- - Issues: https://github.com/gitset-dev/gitset-cli/issues
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
- - Thanks to all our contributors
155
- - Built with [Commander.js](https://github.com/tj/commander.js)
156
- - Powered by Google's Gemini Pro
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitset-dev/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Generate semantic commit messages using AI-driven analysis of staged code changes.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
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
- async function getLastCommits(count = 20) {
50
+ function getConfigPath() {
51
+ const localPath = path.join(CONFIG_LOCATIONS.local.dir, CONFIG_LOCATIONS.local.file);
31
52
  try {
32
- log('History', `Fetching last ${count} commits...`);
33
- const { stdout } = await execAsync(`git log -${count} --pretty=format:"%s"`);
34
- const commits = stdout.split('\n');
35
- log('History', `✓ Retrieved ${commits.length} commits`);
36
- return commits;
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
- log('History', `Error fetching commit history: ${error.message}`, true);
39
- return [];
75
+ if (error.code !== 'EEXIST') {
76
+ throw error;
77
+ }
40
78
  }
79
+ return configPath;
41
80
  }
42
81
 
43
- async function getGitDiff(file) {
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
- log('Diff', `Getting differences for ${file}...`);
46
- const { stdout: lastCommit } = await execAsync('git rev-parse HEAD');
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
- const { stdout: currentContent } = await execAsync(`git show :${file}`);
63
- const currentLineCount = currentContent.split('\n').length;
64
- if (currentLineCount > MAX_LINES) {
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
- log('Diff', `Error processing ${file}: ${error.message}`, true);
76
- return null;
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 getRepoInfo() {
81
- try {
82
- log('Repo', 'Retrieving repository information...');
83
- const { stdout: remoteUrl } = await execAsync('git config --get remote.origin.url');
84
- const repoPath = remoteUrl.trim()
85
- .replace('git@github.com:', '')
86
- .replace('https://github.com/', '')
87
- .replace('.git', '');
88
- log('Repo', `✓ Repository identified: ${repoPath}`);
89
- return repoPath;
90
- } catch (error) {
91
- log('Repo', `Error: ${error.message}`, true);
92
- return '';
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', 'Looking for staged files...');
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 = (await Promise.all(
166
- files.map(async ({ status, file }) => {
167
- const diffResult = await getGitDiff(file);
168
- if (!diffResult) return null;
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
- const repoName = await getRepoInfo();
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 fetch('https://gitset-commit-messages.vercel.app/generate-commit-message', {
194
- method: 'POST',
195
- headers: { 'Content-Type': 'application/json' },
196
- body: JSON.stringify({
197
- repo_name: repoName,
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();