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 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
+ }