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.
Files changed (3) hide show
  1. package/README.md +105 -0
  2. package/index.js +228 -0
  3. 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
+ }