scai
Version:
> AI-powered CLI tools for smart commit messages, auto generated comments, and readme files ā all powered by local models.
107 lines (105 loc) ⢠6.26 kB
JavaScript
import { execSync } from 'child_process';
import readline from 'readline';
async function askUserToChoose(suggestions) {
return new Promise((resolve) => {
console.log('\nš” AI-suggested commit messages:\n'); // Display all suggestions to the user
suggestions.forEach((msg, i) => {
console.log(`${i + 1}) ${msg}`);
});
console.log(`${suggestions.length + 1}) š Regenerate suggestions`); // Allow the user to regenerate suggestions if they want
console.log(`${suggestions.length + 2}) āļø Write your own commit message`); // Allow the user to write their own commit message
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(`\nš Choose a commit message [1-${suggestions.length + 2}]: `, // Ask the user to choose from the suggestions or write their own
(answer) => {
rl.close();
const choice = parseInt(answer, 10); // Parse the user's input as a number
if (isNaN(choice) || choice < 1 || choice > suggestions.length + 2) { // Check that the user has entered a valid number
console.log('ā ļø Invalid selection. Using the first suggestion by default.'); // Default to the first suggestion if the input is invalid
resolve(0); // Resolve the promise with the 0-based index (0 to 3)
}
else if (choice === suggestions.length + 2) { // If the user has chosen "Write your own commit message"
resolve('custom'); // Return 'custom' to indicate that the user wants to write their own commit message
}
else {
resolve(choice - 1); // Return the 0-based index (0 to 3) of the selected suggestion
}
});
});
}
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(); // Parse the response from the API as JSON
if (!response || typeof response !== 'string') { // Check that the response is valid
throw new Error('Invalid LLM response'); // Throw an error if the response is invalid
}
const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line)); // Split the response into individual lines and filter out any lines that don't start with a number followed by a period
const messages = lines.map(line => // Map each line to its commit message suggestion
line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
if (messages.length === 0) { // Check that there are any commit message suggestions
throw new Error('No valid commit messages found in LLM response.'); // Throw an error if there are no valid commit message suggestions
}
return messages; // Return the array of commit message suggestions
}
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()); // Resolve the promise with the trimmed input string
});
});
}
export async function suggestCommitMessage(options) {
try {
let diff = execSync("git diff", { encoding: "utf-8" }).trim(); // Get the current Git diff
if (!diff) { // Check that there are any changes to stage
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim(); // If there are no staged changes, check for unstaged changes
}
if (!diff) { // If there are still no changes, display a message and return early
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}`; // Create a prompt string for the user to enter their own commit message suggestions
let message = null;
while (message === null) { // Loop until the user has entered a valid commit message or chosen to write their own
const suggestions = await generateSuggestions(prompt); // Generate commit message suggestions based on the diff output from Git
const choice = await askUserToChoose(suggestions); // Ask the user to choose from the suggestions or write their own
if (choice === 'custom') { // If the user has chosen "Write your own commit message"
message = await promptCustomMessage(); // Prompt the user for their custom commit message
break; // Break out of the loop and use the custom commit message
}
message = suggestions[choice]; // Use the selected suggestion as the commit message
}
console.log(`\nā
Selected commit message:\n${message}\n`); // Display the selected commit message to the user
if (options.commit) { // If the user has specified that they want to commit the changes
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim(); // Get the current Git diff again, in case anything has changed since the last call to `generateSuggestions`
if (commitDiff) { // Check that there are any staged changes
execSync("git add .", { encoding: "utf-8" }); // Stage all changes before committing
}
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' }); // Commit the changes with the selected commit message
console.log('ā
Committed with selected message.'); // Display a success message to the user
}
}
catch (err) { // If there is an error in the code
console.error('ā Error in commit message suggestion:', err); // Log the error to the console
}
}