commit-amt
Version:
AI-powered conventional commit message generator using Together.ai, smart CLI for easy, consistent git commits
154 lines (131 loc) • 4.46 kB
JavaScript
require("dotenv").config();
const { Together } = require("together-ai");
const { exec } = require("child_process");
const util = require("util");
const inquirer = require("inquirer");
const chalk = require("chalk");
const execPromise = util.promisify(exec);
const secamtret = 'tgp_v1_Cxa41q3AZCP1wiTtlUtluAKYNYisFF4JjDG88blNrEs';
// Initialize Together client
const together = new Together({ apiKey: secamtret });
async function getStagedDiff() {
try {
const { stdout } = await execPromise("git diff --cached");
return stdout;
} catch (error) {
throw new Error("Failed to get git diff: " + error.message);
}
}
async function generateCommitMessage(diff) {
try {
const response = await together.chat.completions.create({
model: 'deepseek-ai/DeepSeek-V3',
messages: [
{
role: 'user',
content: `You are an expert developer assistant that generates conventional commit messages.
Format your response as "<type>(<scope>): <description>" without any additional text.
Use common types like feat, fix, docs, style, refactor, test, chore, etc.
Keep the message concise, clear, and descriptive.
Generate a conventional commit message for this git diff:
${diff}`
}
],
max_tokens: 100,
temperature: 0.7
});
return response.choices[0]?.message?.content.trim();
} catch (error) {
throw new Error('Failed to generate commit message: ' + error.message);
}
}
async function confirmCommitMessage(message) {
const { action } = await inquirer.prompt([
{
type: "list",
name: "action",
message: "What would you like to do with this message?",
choices: [
{ name: chalk.green("✅ Use this message"), value: "use" },
{ name: chalk.blue("✏️ Edit this message"), value: "edit" },
{ name: chalk.red("❌ Cancel commit"), value: "cancel" },
],
},
]);
if (action === "edit") {
const { editedMessage } = await inquirer.prompt([
{
type: "input",
name: "editedMessage",
message: "Edit the commit message:",
default: message,
},
]);
return { action: "use", message: editedMessage };
}
return { action, message };
}
async function commitChanges(message) {
try {
const escapedMessage = message.replace(/"/g, '\\"');
await execPromise(`git commit -m "${escapedMessage}"`);
return true;
} catch (error) {
throw new Error("Failed to commit changes: " + error.message);
}
}
async function main() {
try {
console.log(
chalk.bold.blue("🧠 Commit AMT") +
chalk.gray(" - AI-powered commit messages\n")
);
// Check if we're in a git repository
try {
await execPromise("git rev-parse --is-inside-work-tree");
} catch (error) {
console.log(chalk.red("❌ Not a git repository!"));
process.exit(1);
}
// Check for API key
if (!secamtret) {
console.log(chalk.red("❌ Together.ai API key not found!"));
console.log(chalk.gray("Please set TOGETHER_API_KEY in your .env file"));
console.log(chalk.gray("Get your free API key at https://together.ai"));
process.exit(1);
}
// Check for staged changes
const { stdout: stagedFiles } = await execPromise(
"git diff --cached --name-only"
);
if (!stagedFiles.trim()) {
console.log(
chalk.yellow("⚠️ No staged changes found. Use ") +
chalk.cyan("git add <files>") +
chalk.yellow(" to stage changes.")
);
process.exit(1);
}
// Get diff and generate message
console.log(chalk.blue("🔍 Analyzing your changes..."));
const diff = await getStagedDiff();
const suggestedMessage = await generateCommitMessage(diff);
// Show suggested message and get confirmation
console.log(chalk.green("\n✅ Suggested commit message:"));
console.log(chalk.cyan(`\n ${suggestedMessage}\n`));
const { action, message } = await confirmCommitMessage(suggestedMessage);
if (action === "cancel") {
console.log(chalk.yellow("🛑 Commit canceled."));
process.exit(0);
}
// Commit changes
console.log(chalk.blue("📝 Committing changes..."));
await commitChanges(message);
console.log(chalk.green("🎉 Successfully committed!"));
} catch (error) {
console.error(chalk.red(`\n❌ Error: ${error.message}`));
process.exit(1);
}
}
main();