scai
Version:
> AI-powered CLI tools for smart commit messages, auto generated comments, and readme files ā all powered by local models.
110 lines (108 loc) ⢠4.13 kB
JavaScript
import { execSync } from 'child_process';
import readline from 'readline';
function askUserToChoose(suggestions) {
return new Promise((resolve) => {
console.log('\nš” AI-suggested commit messages:\n');
suggestions.forEach((msg, i) => {
console.log(`${i + 1}) ${msg}`);
});
console.log(`${suggestions.length + 1}) š Regenerate suggestions`);
console.log(`${suggestions.length + 2}) āļø Write your own commit message`);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(`\nš Choose a commit message [1-${suggestions.length + 2}]: `, (answer) => {
rl.close();
const choice = parseInt(answer, 10);
if (isNaN(choice) || choice < 1 || choice > suggestions.length + 2) {
console.log('ā ļø Invalid selection. Using the first suggestion by default.');
resolve(0);
}
else if (choice === suggestions.length + 2) {
resolve('custom');
}
else {
resolve(choice - 1); // Return 0-based index (0 to 3)
}
});
});
}
async function generateSuggestions(prompt) {
const res = await fetch("http://localhost:11434/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "llama3",
prompt,
stream: false,
}),
});
const { response } = await res.json();
if (!response || typeof response !== 'string') {
throw new Error('Invalid LLM response');
}
const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line));
const messages = lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
if (messages.length === 0) {
throw new Error('No valid commit messages found in LLM response.');
}
return messages;
}
function promptCustomMessage() {
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('\nš Enter your custom commit message:\n> ', (input) => {
rl.close();
resolve(input.trim());
});
});
}
export async function suggestCommitMessage(options) {
try {
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
if (!diff) {
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
}
if (!diff) {
console.log('ā ļø No staged changes to suggest a message for.');
return;
}
const prompt = `Suggest 3 concise, conventional Git commit message options for this diff. Return ONLY the commit messages, numbered 1 to 3, like so:
1. ...
2. ...
3. ...
Here is the diff:
${diff}`;
let message = null;
while (message === null) {
const suggestions = await generateSuggestions(prompt);
const choice = await askUserToChoose(suggestions);
if (choice === suggestions.length) {
// User chose "Regenerate"
console.log('\nš Regenerating suggestions...\n');
continue;
}
if (choice === 'custom') {
message = await promptCustomMessage();
break;
}
message = suggestions[choice];
}
console.log(`\nā
Selected commit message:\n${message}\n`);
if (options.commit) {
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim();
if (commitDiff) {
execSync("git add .", { encoding: "utf-8" });
}
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
console.log('ā
Committed with selected message.');
}
}
catch (err) {
console.error('ā Error in commit message suggestion:', err.message);
}
}