git-aic 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/dist/cli.js +59 -0
- package/dist/confirm.js +25 -0
- package/dist/git.js +20 -0
- package/dist/llm.js +49 -0
- package/dist/prompt.js +44 -0
- package/package.json +46 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { simpleGit } from "simple-git";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { getGitDiff } from "./git.js";
|
|
6
|
+
import { generateCommitMessage } from "./llm.js";
|
|
7
|
+
import { getUserConfirmation } from "./confirm.js";
|
|
8
|
+
const git = simpleGit();
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name("commit")
|
|
12
|
+
.description("AI-powered Git commit generator using Google Gemini")
|
|
13
|
+
.version("1.0.0")
|
|
14
|
+
.option("-p, --push", "push after committing");
|
|
15
|
+
program.action(async (options) => {
|
|
16
|
+
try {
|
|
17
|
+
const diff = await getGitDiff();
|
|
18
|
+
if (!diff) {
|
|
19
|
+
console.log(chalk.yellow("No changes to commit!"));
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
const status = await git.status();
|
|
23
|
+
console.log(chalk.blue("\nFiles being committed:"));
|
|
24
|
+
status.staged.forEach((file) => console.log(chalk.cyan(`- ${file}`)));
|
|
25
|
+
console.log("");
|
|
26
|
+
let currentMessage = "";
|
|
27
|
+
let confirmed = false;
|
|
28
|
+
console.log(chalk.blue("Analyzing staged changes...\n"));
|
|
29
|
+
while (!confirmed) {
|
|
30
|
+
currentMessage = await generateCommitMessage(diff);
|
|
31
|
+
const { choice, message } = await getUserConfirmation(currentMessage);
|
|
32
|
+
if (choice === 'y') {
|
|
33
|
+
currentMessage = message;
|
|
34
|
+
confirmed = true;
|
|
35
|
+
}
|
|
36
|
+
else if (choice === 'r') {
|
|
37
|
+
console.log(chalk.yellow("Regenerating...\n"));
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(chalk.red("Aborted."));
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
console.log(chalk.blue(`> ran: git commit -m "${currentMessage}"`));
|
|
46
|
+
await git.commit(currentMessage);
|
|
47
|
+
console.log(chalk.green("\nCommit successful"));
|
|
48
|
+
if (options.push) {
|
|
49
|
+
console.log(chalk.blue("> ran: git push"));
|
|
50
|
+
await git.push();
|
|
51
|
+
console.log(chalk.green("Push successful"));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error(chalk.red("\nCommit failed:"), error);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
program.parse(process.argv);
|
package/dist/confirm.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
export const getUserConfirmation = async (message) => {
|
|
4
|
+
const rl = readline.createInterface({
|
|
5
|
+
input: process.stdin,
|
|
6
|
+
output: process.stdout,
|
|
7
|
+
});
|
|
8
|
+
console.log(chalk.green(`\nProposed: "${message}"`));
|
|
9
|
+
const answer = await rl.question(chalk.blue('Confirm commit? [y=yes, n=no, r=retry, e=edit]: '));
|
|
10
|
+
rl.close();
|
|
11
|
+
const choice = (answer.toLowerCase() || 'y').trim();
|
|
12
|
+
if (choice === 'e') {
|
|
13
|
+
const editRl = readline.createInterface({
|
|
14
|
+
input: process.stdin,
|
|
15
|
+
output: process.stdout,
|
|
16
|
+
terminal: true
|
|
17
|
+
});
|
|
18
|
+
console.log(chalk.cyan("\nEdit the message:"));
|
|
19
|
+
editRl.write(message);
|
|
20
|
+
const editedMessage = await editRl.question("> ");
|
|
21
|
+
editRl.close();
|
|
22
|
+
return { choice: 'y', message: editedMessage };
|
|
23
|
+
}
|
|
24
|
+
return { choice, message };
|
|
25
|
+
};
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { simpleGit } from "simple-git";
|
|
2
|
+
const git = simpleGit();
|
|
3
|
+
export const getGitDiff = async () => {
|
|
4
|
+
try {
|
|
5
|
+
await git.raw(["config", "core.autocrlf", "true"]);
|
|
6
|
+
let diff = await git.diff(["--cached", "--ignore-space-at-eol"]);
|
|
7
|
+
if (!diff) {
|
|
8
|
+
console.log("No staged changes detected. Auto-staging all files...");
|
|
9
|
+
await git.add(".");
|
|
10
|
+
diff = await git.diff(["--cached", "--ignore-space-at-eol"]);
|
|
11
|
+
if (!diff)
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
return diff;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.error(error);
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
};
|
package/dist/llm.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { buildPrompt } from "./prompt.js";
|
|
4
|
+
export const generateCommitMessage = async (rawDiff) => {
|
|
5
|
+
const API_URL = "https://generativelanguage.googleapis.com/v1/models/gemini-2.5-flash:generateContent";
|
|
6
|
+
const API_KEY = process.env.GEMINI_COMMIT_MESSAGE_API_KEY;
|
|
7
|
+
if (!API_KEY) {
|
|
8
|
+
console.error(chalk.red("\nMissing GEMINI_COMMIT_MESSAGE_API_KEY environment variable.\n"));
|
|
9
|
+
console.log("Please set your API key before running this command.\n");
|
|
10
|
+
console.log(chalk.yellow("How to fix this:\n"));
|
|
11
|
+
console.log(chalk.cyan("macOS / Linux:"));
|
|
12
|
+
console.log(" export GEMINI_COMMIT_MESSAGE_API_KEY=your_api_key_here\n");
|
|
13
|
+
console.log(chalk.cyan("Windows (PowerShell):"));
|
|
14
|
+
console.log(' setx GEMINI_COMMIT_MESSAGE_API_KEY "your_api_key_here"\n');
|
|
15
|
+
console.log(chalk.gray("After setting the variable, restart your terminal.\n"));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const prompt = buildPrompt(rawDiff);
|
|
19
|
+
try {
|
|
20
|
+
const response = await axios.post(API_URL, {
|
|
21
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
22
|
+
}, {
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
"x-goog-api-key": API_KEY,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
return (response.data.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ||
|
|
29
|
+
"chore: update code");
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (axios.isAxiosError(error)) {
|
|
33
|
+
if (error.response) {
|
|
34
|
+
const apiMessage = error.response.data?.error?.message || error.message;
|
|
35
|
+
console.error(chalk.red(`LLM request failed: ${apiMessage}`));
|
|
36
|
+
}
|
|
37
|
+
else if (error.request) {
|
|
38
|
+
console.error(chalk.red("Network error: Could not connect to Google API. Check your internet."));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.error(chalk.red(`An unknown error occurred during the LLM request: ${error.message}`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.error(chalk.red("Unexpected Error:"), error);
|
|
46
|
+
}
|
|
47
|
+
return "chore: update code";
|
|
48
|
+
}
|
|
49
|
+
};
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const buildPrompt = (diff) => `
|
|
2
|
+
CRITICAL INSTRUCTIONS - READ CAREFULLY:
|
|
3
|
+
You are an expert Git commit message writer. You MUST follow ALL these rules:
|
|
4
|
+
|
|
5
|
+
1. FORMAT: Use Conventional Commits format: <type>(<scope>): <description>
|
|
6
|
+
- type: MUST be one of: feat, fix, refactor, chore, docs, style, test, perf
|
|
7
|
+
- scope: Should be the module/file affected (e.g., "auth", "api", "ui", "config")
|
|
8
|
+
- description: Clear, imperative description in present tense
|
|
9
|
+
|
|
10
|
+
2. DESCRIPTION REQUIREMENTS:
|
|
11
|
+
- Start with an imperative verb (add, fix, remove, update, refactor, etc.)
|
|
12
|
+
- Be specific about what changed
|
|
13
|
+
- Keep it under 72 characters total (including type and scope)
|
|
14
|
+
- NO trailing punctuation
|
|
15
|
+
- NO emojis ever
|
|
16
|
+
- MUST be a complete sentence
|
|
17
|
+
|
|
18
|
+
3. MESSAGE STRUCTURE:
|
|
19
|
+
- The entire commit message must be exactly one line
|
|
20
|
+
- Format: type(scope): description
|
|
21
|
+
- Example: "feat(auth): add password reset functionality"
|
|
22
|
+
- Example: "fix(api): handle null response in user endpoint"
|
|
23
|
+
- Example: "refactor(ui): simplify component state management"
|
|
24
|
+
|
|
25
|
+
4. QUALITY CHECKS - YOUR OUTPUT MUST PASS:
|
|
26
|
+
- Contains opening and closing parentheses
|
|
27
|
+
- Has a colon after the parentheses
|
|
28
|
+
- Description exists and is not empty
|
|
29
|
+
- Total length ≤ 72 characters
|
|
30
|
+
- No markdown formatting
|
|
31
|
+
- No code blocks
|
|
32
|
+
- No explanations or notes
|
|
33
|
+
|
|
34
|
+
5. FAILURE MODE:
|
|
35
|
+
- If you cannot generate a proper message, return exactly: "chore: update code"
|
|
36
|
+
|
|
37
|
+
YOUR TASK:
|
|
38
|
+
Analyze this git diff and generate exactly ONE proper commit message following all rules above.
|
|
39
|
+
|
|
40
|
+
Git diff:
|
|
41
|
+
${diff}
|
|
42
|
+
|
|
43
|
+
Commit message:
|
|
44
|
+
`.trim();
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "git-aic",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered Git commit generator using Google Gemini",
|
|
5
|
+
"homepage": "https://github.com/samueltuoyo15/Commit-Message-Tool/tree/#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/samueltuoyo15/Commit-Message-Tool/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/samueltuoyo15/Commit-Message-Tool.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"author": "Spectra010s",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/cli.js",
|
|
17
|
+
"bin": {
|
|
18
|
+
"git-aic": "./dist/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"prepare": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"commit message tool",
|
|
29
|
+
"git",
|
|
30
|
+
"git-plugin",
|
|
31
|
+
"git-aic",
|
|
32
|
+
"automated-commits",
|
|
33
|
+
"ai-commits",
|
|
34
|
+
"gemini"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"axios": "^1.13.2",
|
|
38
|
+
"chalk": "^5.6.2",
|
|
39
|
+
"commander": "^14.0.2",
|
|
40
|
+
"simple-git": "^3.30.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.0.3",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
}
|
|
46
|
+
}
|