git-smart-commit-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/ai.js +46 -0
- package/config.js +25 -0
- package/git-engine.js +18 -0
- package/index.js +105 -0
- package/package.json +19 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emirhan Oğuz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Git Smart Commit (ai-commit-cli)
|
|
2
|
+
|
|
3
|
+
An AI-powered Git commit message generator. Stop writing repetitive commit messages and let AI generate conventional, descriptive commits for you based on your staged code changes.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **Bring Your Own Key (BYOK):** Securely stores your free API key locally (`~/.ai-commit/config.json`).
|
|
7
|
+
- **Google Gemini API Support:** Blazing fast generation using the latest Gemini models.
|
|
8
|
+
- **Ollama Support (Coming Soon):** Generate commits locally with zero API keys or internet connection.
|
|
9
|
+
- **Interactive UI:** Review, edit, or regenerate the commit message before applying it.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
*(Note: Currently in development. Global npm install coming soon.)*
|
|
14
|
+
|
|
15
|
+
Clone the repository and install globally:
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/yourusername/ai-commit-cli.git
|
|
18
|
+
cd ai-commit-cli
|
|
19
|
+
npm install
|
|
20
|
+
npm link
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
First, configure your AI provider (Currently supports Google Gemini):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ai-commit config
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
You will be prompted to enter your Gemini API key.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
1. Stage your changes:
|
|
36
|
+
```bash
|
|
37
|
+
git add .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
2. Run the tool:
|
|
41
|
+
```bash
|
|
42
|
+
ai-commit
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The tool will analyze your `git diff --staged` and generate a commit message following the Conventional Commits specification.
|
|
46
|
+
|
|
47
|
+
### Interactive Menu
|
|
48
|
+
Once the message is generated, you will see a prompt:
|
|
49
|
+
- **Accept and Commit:** Automatically runs `git commit -m "..."`
|
|
50
|
+
- **Regenerate:** Asks the AI for another option
|
|
51
|
+
- **Edit manually:** Opens an input field to tweak the AI's suggestion
|
|
52
|
+
- **Cancel:** Aborts the commit
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
MIT
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
<!-- test trigger -->
|
|
59
|
+
.
|
package/ai.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
|
|
3
|
+
const PROMPT = `You are an expert developer. Generate a concise, conventional git commit message based on the provided git diff.
|
|
4
|
+
Follow the conventional commits format (e.g., feat: ..., fix: ..., chore: ...).
|
|
5
|
+
Do not include any explanation, markdown formatting, or quotes around the message. Just output the commit message string.
|
|
6
|
+
|
|
7
|
+
Git diff:
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
export async function generateCommitMessage(config, diff) {
|
|
11
|
+
const provider = config.provider || 'gemini';
|
|
12
|
+
|
|
13
|
+
if (provider === 'gemini') {
|
|
14
|
+
if (!config.apiKey) throw new Error('Gemini API key is missing. Run config first.');
|
|
15
|
+
const genAI = new GoogleGenerativeAI(config.apiKey);
|
|
16
|
+
|
|
17
|
+
// Model fallback list in case of 503 High Demand errors
|
|
18
|
+
const modelsToTry = ["gemini-flash-latest", "gemini-pro-latest", "gemini-2.5-flash", "gemini-3.5-flash"];
|
|
19
|
+
let lastError = null;
|
|
20
|
+
|
|
21
|
+
for (const modelName of modelsToTry) {
|
|
22
|
+
try {
|
|
23
|
+
const model = genAI.getGenerativeModel({ model: modelName });
|
|
24
|
+
const result = await model.generateContent(PROMPT + diff);
|
|
25
|
+
return result.response.text().trim();
|
|
26
|
+
} catch (error) {
|
|
27
|
+
lastError = error;
|
|
28
|
+
// If it's a 503 or 500 error, continue to the next model
|
|
29
|
+
if (error.message && (error.message.includes('503') || error.message.includes('500') || error.message.includes('demand'))) {
|
|
30
|
+
console.log(`\nModel ${modelName} is busy, switching to fallback model...`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// If it's a different error (like invalid key), throw immediately
|
|
34
|
+
throw new Error(`Failed to generate message from Gemini API (${modelName}): ` + error.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
throw new Error('All Gemini fallback models failed due to high demand. Please try again later. Last error: ' + lastError.message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (provider === 'ollama') {
|
|
42
|
+
throw new Error('Ollama provider is not fully implemented yet.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new Error('Unknown AI provider selected.');
|
|
46
|
+
}
|
package/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.ai-commit');
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
export function loadConfig() {
|
|
9
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
10
|
+
try {
|
|
11
|
+
const data = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
12
|
+
return JSON.parse(data);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function saveConfig(config) {
|
|
21
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
22
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
25
|
+
}
|
package/git-engine.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { execSync, execFileSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
export function getStagedDiff() {
|
|
4
|
+
try {
|
|
5
|
+
const diff = execSync('git --no-pager diff --staged', { encoding: 'utf8' });
|
|
6
|
+
return diff.trim();
|
|
7
|
+
} catch (error) {
|
|
8
|
+
throw new Error('Failed to run git diff. Are you in a git repository?');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function commitChanges(message) {
|
|
13
|
+
try {
|
|
14
|
+
execFileSync('git', ['commit', '-m', message], { stdio: 'inherit' });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new Error('Failed to commit changes.');
|
|
17
|
+
}
|
|
18
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
7
|
+
import { getStagedDiff, commitChanges } from './git-engine.js';
|
|
8
|
+
import { generateCommitMessage } from './ai.js';
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('ai-commit')
|
|
12
|
+
.description('AI-powered git commit message generator')
|
|
13
|
+
.version('1.0.0');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('config')
|
|
17
|
+
.description('Set up your Gemini API key')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
const answers = await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'password',
|
|
22
|
+
name: 'apiKey',
|
|
23
|
+
message: 'Enter your Google Gemini API key:',
|
|
24
|
+
mask: '*'
|
|
25
|
+
}
|
|
26
|
+
]);
|
|
27
|
+
const config = loadConfig();
|
|
28
|
+
config.apiKey = answers.apiKey;
|
|
29
|
+
saveConfig(config);
|
|
30
|
+
console.log(chalk.green('API Key saved successfully!'));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
program
|
|
34
|
+
.command('commit', { isDefault: true })
|
|
35
|
+
.description('Generate a commit message and commit')
|
|
36
|
+
.action(async () => {
|
|
37
|
+
const config = loadConfig();
|
|
38
|
+
if (!config.apiKey) {
|
|
39
|
+
console.log(chalk.red('Error: API key not found.'));
|
|
40
|
+
console.log(`Please run ${chalk.yellow('ai-commit config')} to set it up.`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const diff = getStagedDiff();
|
|
46
|
+
if (!diff) {
|
|
47
|
+
console.log(chalk.yellow('No staged changes found. Please stage your changes using "git add" first.'));
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let message = '';
|
|
52
|
+
let accepted = false;
|
|
53
|
+
|
|
54
|
+
while (!accepted) {
|
|
55
|
+
console.log(chalk.blue('\nAnalyzing git diff...'));
|
|
56
|
+
message = await generateCommitMessage(config, diff);
|
|
57
|
+
|
|
58
|
+
console.log('\n' + chalk.bold('Generated Commit Message:'));
|
|
59
|
+
console.log(chalk.green(message) + '\n');
|
|
60
|
+
|
|
61
|
+
const { action } = await inquirer.prompt([
|
|
62
|
+
{
|
|
63
|
+
type: 'list',
|
|
64
|
+
name: 'action',
|
|
65
|
+
message: 'What would you like to do?',
|
|
66
|
+
choices: [
|
|
67
|
+
{ name: 'Accept and Commit', value: 'accept' },
|
|
68
|
+
{ name: 'Regenerate', value: 'regenerate' },
|
|
69
|
+
{ name: 'Edit manually', value: 'edit' },
|
|
70
|
+
{ name: 'Cancel', value: 'cancel' }
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
if (action === 'accept') {
|
|
76
|
+
accepted = true;
|
|
77
|
+
} else if (action === 'edit') {
|
|
78
|
+
const { newMessage } = await inquirer.prompt([
|
|
79
|
+
{
|
|
80
|
+
type: 'input',
|
|
81
|
+
name: 'newMessage',
|
|
82
|
+
message: 'Edit commit message:',
|
|
83
|
+
default: message
|
|
84
|
+
}
|
|
85
|
+
]);
|
|
86
|
+
message = newMessage;
|
|
87
|
+
accepted = true;
|
|
88
|
+
} else if (action === 'cancel') {
|
|
89
|
+
console.log(chalk.yellow('Commit cancelled.'));
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
// If regenerate, loop continues
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(chalk.blue('Committing...'));
|
|
96
|
+
commitChanges(message);
|
|
97
|
+
console.log(chalk.green('Successfully committed!'));
|
|
98
|
+
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(chalk.red(error.message));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "git-smart-commit-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered git commit message generator",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ai-commit": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@google/generative-ai": "^0.21.0",
|
|
15
|
+
"chalk": "^5.3.0",
|
|
16
|
+
"commander": "^12.1.0",
|
|
17
|
+
"inquirer": "^10.2.2"
|
|
18
|
+
}
|
|
19
|
+
}
|