git-mood 2.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/README.md +105 -0
- package/index.js +228 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# git-mood
|
|
2
|
+
|
|
3
|
+
**AI-powered Git assistant** — generate conventional commit messages and run quick code reviews using Google Gemini.
|
|
4
|
+
|
|
5
|
+
- **commit** — AI suggests a commit message from your staged diff, then commit (and optionally push).
|
|
6
|
+
- **review** — AI reviews your current diff for bugs, security, and clean-code tips.
|
|
7
|
+
- **model** — Switch between Gemini Flash-Lite, Flash 2.5, and Flash 3.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g git-mood
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or run without installing (with `npx`):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx git-mood setup
|
|
21
|
+
npx git-mood commit
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Setup (first-time)
|
|
27
|
+
|
|
28
|
+
Run once to store your **Google Gemini API key** and pick a **model**:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git-mood setup
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
You’ll be prompted for:
|
|
35
|
+
|
|
36
|
+
1. **API key** — Paste your key (plain input so you can paste). Get one at [Google AI Studio](https://aistudio.google.com/apikey). It’s stored locally (see [How API key is stored](#how-api-key-is-stored)).
|
|
37
|
+
2. **Model** — Use **↑/↓ arrow keys** to choose, **Enter** to select:
|
|
38
|
+
- **Flash-Lite 2.5** — New & lightest
|
|
39
|
+
- **Flash 2.5** — Fast & balanced (default)
|
|
40
|
+
- **Flash 3** — Newest
|
|
41
|
+
|
|
42
|
+
Change the model later with:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git-mood model
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
(Same arrow-key list; choose and press Enter.)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## How to use
|
|
53
|
+
|
|
54
|
+
### Generate commit message and commit
|
|
55
|
+
|
|
56
|
+
1. Stage your changes: `git add .` (or specific files).
|
|
57
|
+
2. Run:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git-mood commit
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
3. git-mood analyzes the diff, suggests a **Conventional Commits**-style message, and asks to confirm.
|
|
64
|
+
4. After committing, it can push to the remote (with optional pull-if-needed).
|
|
65
|
+
|
|
66
|
+
### Code review (no commit)
|
|
67
|
+
|
|
68
|
+
Review current working + staged diff for bugs, security, and improvements:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git-mood review
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Commands
|
|
77
|
+
|
|
78
|
+
| Command | Description |
|
|
79
|
+
|----------|------------------------------------------|
|
|
80
|
+
| `git-mood setup` | Set Gemini API key and model (first-time). API key = plain input (paste OK). Model = arrow keys + Enter. |
|
|
81
|
+
| `git-mood commit` | Generate commit message from staged diff, commit, optional push. |
|
|
82
|
+
| `git-mood review` | AI code review of current diff. |
|
|
83
|
+
| `git-mood model` | Change Gemini model (arrow keys + Enter to select). |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## How API key is stored
|
|
88
|
+
|
|
89
|
+
- git-mood uses [conf](https://github.com/sindresus/conf) to store config on your machine (project name: `git-mood`).
|
|
90
|
+
- Stored values: `gemini_key` (your API key) and `model_id` (e.g. `gemini-2.5-flash`).
|
|
91
|
+
- Location is OS-specific (e.g. `%APPDATA%\git-mood\config.json` on Windows, `~/.config/git-mood/` on Linux/macOS).
|
|
92
|
+
- The key is **only used to call Google’s Gemini API** from your machine; it isn’t sent to any other service.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Requirements
|
|
97
|
+
|
|
98
|
+
- **Node.js** 18+
|
|
99
|
+
- **Git** repo (run commands from a repo with staged or unstaged changes as needed).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
ISC
|
package/index.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import simpleGit from 'simple-git';
|
|
4
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import Conf from 'conf';
|
|
8
|
+
|
|
9
|
+
const config = new Conf({ projectName: 'git-mood' });
|
|
10
|
+
const git = simpleGit();
|
|
11
|
+
|
|
12
|
+
const MODELS = [
|
|
13
|
+
{ id: 'gemini-2.5-flash-lite', name: 'Flash-Lite 2.5 (New & Lightest)' },
|
|
14
|
+
{ id: 'gemini-2.5-flash', name: 'Flash 2.5 (Fast & Balanced)' },
|
|
15
|
+
{ id: 'gemini-3-flash-preview', name: 'Flash 3 (Newest)' },
|
|
16
|
+
];
|
|
17
|
+
const DEFAULT_MODEL = 'gemini-2.5-flash';
|
|
18
|
+
|
|
19
|
+
function getModelId() {
|
|
20
|
+
return config.get('model_id') ?? DEFAULT_MODEL;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// --- HELPER: GET AI MODEL ---
|
|
24
|
+
function getAI() {
|
|
25
|
+
const apiKey = config.get('gemini_key');
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
console.log(chalk.red("❌ No API Key found! Run 'git-mood setup' first."));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const genAI = new GoogleGenerativeAI(apiKey);
|
|
31
|
+
const modelId = getModelId();
|
|
32
|
+
return genAI.getGenerativeModel({ model: modelId });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --- COMMAND 1: AUTO COMMIT & PUSH ---
|
|
36
|
+
async function generateCommit() {
|
|
37
|
+
try {
|
|
38
|
+
// 1. Check staged files
|
|
39
|
+
const diff = await git.diff(['--staged']);
|
|
40
|
+
|
|
41
|
+
if (!diff) {
|
|
42
|
+
console.log(chalk.yellow("⚠️ No staged changes found. Did you run 'git add .'?"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
process.stdout.write(chalk.blue("🧠 Analyzing changes..."));
|
|
47
|
+
|
|
48
|
+
const model = getAI();
|
|
49
|
+
// Prompt asking for a conventional commit message
|
|
50
|
+
const prompt = `
|
|
51
|
+
You are an expert developer. Write a single, concise git commit message for these changes.
|
|
52
|
+
Follow "Conventional Commits" format (e.g., 'feat: add login', 'fix: resolve crash').
|
|
53
|
+
Do not add quotes or extra text. Just the message.
|
|
54
|
+
|
|
55
|
+
THE DIFF:
|
|
56
|
+
${diff.substring(0, 5000)}
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
const result = await model.generateContent(prompt);
|
|
60
|
+
const message = result.response.text().trim();
|
|
61
|
+
console.log("\r" + " ".repeat(50) + "\r"); // Clear spinner
|
|
62
|
+
|
|
63
|
+
console.log(chalk.green("Suggested Message: ") + chalk.bold.white(message));
|
|
64
|
+
|
|
65
|
+
// 2. Ask user to confirm COMMIT
|
|
66
|
+
const commitAnswer = await inquirer.prompt([
|
|
67
|
+
{
|
|
68
|
+
type: 'confirm',
|
|
69
|
+
name: 'confirm',
|
|
70
|
+
message: 'Commit with this message?',
|
|
71
|
+
default: true
|
|
72
|
+
}
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
if (commitAnswer.confirm) {
|
|
76
|
+
await git.commit(message);
|
|
77
|
+
console.log(chalk.green("✅ Committed locally!"));
|
|
78
|
+
|
|
79
|
+
// 3. NEW STEP: Ask user to PUSH
|
|
80
|
+
const pushAnswer = await inquirer.prompt([
|
|
81
|
+
{
|
|
82
|
+
type: 'confirm',
|
|
83
|
+
name: 'shouldPush',
|
|
84
|
+
message: '🚀 Do you want to push to GitHub now?',
|
|
85
|
+
default: true
|
|
86
|
+
}
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
if (pushAnswer.shouldPush) {
|
|
90
|
+
process.stdout.write(chalk.yellow("🚀 Pushing code..."));
|
|
91
|
+
try {
|
|
92
|
+
await git.push();
|
|
93
|
+
console.log("\r" + " ".repeat(50) + "\r");
|
|
94
|
+
console.log(chalk.green.bold("🎉 Pushed to GitHub successfully!"));
|
|
95
|
+
} catch (pushError) {
|
|
96
|
+
// Check if the error is because we need to pull
|
|
97
|
+
if (pushError.message.includes('fetch first') || pushError.message.includes('rejected')) {
|
|
98
|
+
console.log(chalk.yellow("\n⚠️ GitHub is ahead of your computer."));
|
|
99
|
+
|
|
100
|
+
const pullAnswer = await inquirer.prompt([
|
|
101
|
+
{
|
|
102
|
+
type: 'confirm',
|
|
103
|
+
name: 'shouldPull',
|
|
104
|
+
message: 'Do you want to PULL (download) changes and try pushing again?',
|
|
105
|
+
default: true
|
|
106
|
+
}
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
if (pullAnswer.shouldPull) {
|
|
110
|
+
try {
|
|
111
|
+
console.log(chalk.blue("⬇️ Pulling changes..."));
|
|
112
|
+
await git.pull();
|
|
113
|
+
console.log(chalk.blue("⬆️ Pushing again..."));
|
|
114
|
+
await git.push();
|
|
115
|
+
console.log(chalk.green.bold("🎉 Pushed to GitHub successfully!"));
|
|
116
|
+
} catch (pullError) {
|
|
117
|
+
console.error(chalk.red("\n❌ Auto-fix failed. You likely have merge conflicts. Fix them manually."));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
console.error(chalk.red("\n❌ Push failed:"), pushError.message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
} else {
|
|
127
|
+
console.log(chalk.yellow("❌ Cancelled."));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.error(chalk.red("Error:"), e.message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- COMMAND 2: CODE REVIEW ---
|
|
136
|
+
async function codeReview() {
|
|
137
|
+
try {
|
|
138
|
+
// Look at unstaged AND staged changes
|
|
139
|
+
const diff = await git.diff();
|
|
140
|
+
|
|
141
|
+
if (!diff) {
|
|
142
|
+
console.log(chalk.green("✨ No changes to review. Working directory clean."));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
process.stdout.write(chalk.magenta("🕵️ Scanning code for bugs and smell..."));
|
|
147
|
+
|
|
148
|
+
const model = getAI();
|
|
149
|
+
const prompt = `
|
|
150
|
+
Review this code diff like a Senior Engineer.
|
|
151
|
+
1. Identify potential bugs (logic errors, memory leaks).
|
|
152
|
+
2. Point out security risks (exposed keys, unsafe inputs).
|
|
153
|
+
3. Suggest 1 clean code improvement.
|
|
154
|
+
|
|
155
|
+
Format output as a bulleted list. Be helpful but strict.
|
|
156
|
+
|
|
157
|
+
THE DIFF:
|
|
158
|
+
${diff.substring(0, 8000)}
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
const result = await model.generateContent(prompt);
|
|
162
|
+
console.log("\r" + " ".repeat(50) + "\r");
|
|
163
|
+
|
|
164
|
+
console.log(chalk.bold.magenta("\n🛡️ AI CODE REVIEW REPORT 🛡️"));
|
|
165
|
+
console.log(result.response.text());
|
|
166
|
+
|
|
167
|
+
} catch (e) {
|
|
168
|
+
console.error(chalk.red("Error:"), e.message);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// --- COMMAND 3: SETUP ---
|
|
173
|
+
async function setupCLI() {
|
|
174
|
+
const answers = await inquirer.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: 'input',
|
|
177
|
+
name: 'apiKey',
|
|
178
|
+
message: 'Paste your Google Gemini API Key:',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
type: 'select',
|
|
182
|
+
name: 'modelId',
|
|
183
|
+
message: 'Choose Gemini model (↑/↓ arrows, Enter to select):',
|
|
184
|
+
choices: MODELS.map((m) => ({ name: m.name, value: m.id })),
|
|
185
|
+
default: getModelId(),
|
|
186
|
+
},
|
|
187
|
+
]);
|
|
188
|
+
config.set('gemini_key', answers.apiKey);
|
|
189
|
+
config.set('model_id', answers.modelId);
|
|
190
|
+
console.log(chalk.green("✅ API Key and model saved."));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// --- COMMAND 4: MODEL (change model) ---
|
|
194
|
+
async function modelCLI() {
|
|
195
|
+
const answer = await inquirer.prompt([
|
|
196
|
+
{
|
|
197
|
+
type: 'select',
|
|
198
|
+
name: 'modelId',
|
|
199
|
+
message: 'Choose Gemini model (↑/↓ arrows, Enter to select):',
|
|
200
|
+
choices: MODELS.map((m) => ({ name: m.name, value: m.id })),
|
|
201
|
+
default: getModelId(),
|
|
202
|
+
},
|
|
203
|
+
]);
|
|
204
|
+
config.set('model_id', answer.modelId);
|
|
205
|
+
const label = MODELS.find((m) => m.id === answer.modelId)?.name ?? answer.modelId;
|
|
206
|
+
console.log(chalk.green("✅ Model set to: " + label));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- CLI CONFIG ---
|
|
210
|
+
program
|
|
211
|
+
.name('git-mood')
|
|
212
|
+
.description('AI-Powered Git Assistant — conventional commits & code review')
|
|
213
|
+
.version('2.0.0');
|
|
214
|
+
|
|
215
|
+
program.command('setup').description('Set Gemini API key and model').action(setupCLI);
|
|
216
|
+
program.command('model').description('Change Gemini model').action(modelCLI);
|
|
217
|
+
|
|
218
|
+
program
|
|
219
|
+
.command('commit')
|
|
220
|
+
.description('Generates a commit message from your staged changes and commits it')
|
|
221
|
+
.action(generateCommit);
|
|
222
|
+
|
|
223
|
+
program
|
|
224
|
+
.command('review')
|
|
225
|
+
.description('Scans your current changes for bugs before you commit')
|
|
226
|
+
.action(codeReview);
|
|
227
|
+
|
|
228
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "git-mood",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "AI-powered Git assistant — conventional commits & code review with Gemini",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"git-mood": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cli",
|
|
15
|
+
"git",
|
|
16
|
+
"ai",
|
|
17
|
+
"gemini",
|
|
18
|
+
"commit",
|
|
19
|
+
"code-review",
|
|
20
|
+
"conventional-commits"
|
|
21
|
+
],
|
|
22
|
+
"author": "Eyuel Engida",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/Eul45/git-mood.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/eul45/git-mood#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/Eul45/git-mood/issues"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@google/generative-ai": "^0.2.1",
|
|
40
|
+
"chalk": "^5.3.0",
|
|
41
|
+
"commander": "^12.0.0",
|
|
42
|
+
"conf": "^12.0.0",
|
|
43
|
+
"inquirer": "^9.2.14",
|
|
44
|
+
"simple-git": "^3.22.0"
|
|
45
|
+
}
|
|
46
|
+
}
|