diff --git a/README.md b/README.md index e911f8d..fad76f1 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Now: After: +- [ ] Rewrite in TypeScript - [ ] Look into better conventional commit support, look at other CLIs - [ ] Try supporting more than 200 lines by grabbing the diff per file - [ ] Build landing page diff --git a/aicommit.js b/aicommit.js new file mode 100755 index 0000000..473aa1a --- /dev/null +++ b/aicommit.js @@ -0,0 +1,82 @@ +#!/usr/bin/env zx + +void (async function () { + $.verbose = false; + + // TODO: Figure out how to fail gracefully instead of exit 1 + + let conventionalCommit = false; + + console.log(chalk.white("▲ ") + chalk.green("Welcome to AICommit!")); + + let pwd = await $`cd ~ && pwd;`; + + let { OPENAI_API_KEY } = await fs.readJson( + `${pwd.stdout.trim()}/ai-commit/.env.json` + ); + let diff = await $`git diff --cached`; + + // Accounting for GPT-3's input req of 4k tokens (approx 8k chars) + if (diff.stdout.length > 8000) { + console.log("The diff is too large to write a commit message."); + await $`exit 1`; + } + + if (diff.stdout.length === 0) { + console.log( + "No staged changes found. Make sure there are changes and run `git add .`" + ); + await $`exit 1`; + } + + let prompt = `I want you to act like a git commit message writer. I will input a git diff and your job is to convert it into a useful commit message. ${ + conventionalCommit + ? "Preface the commit with 'feat:' if it is a feature or 'fix:' if it is a bug." + : "Do not preface the commit with anything." + } Return a complete sentence and do not repeat yourself: ${diff}`; + + const payload = { + model: "text-davinci-003", + prompt, + temperature: 0.7, + top_p: 1, + frequency_penalty: 0, + presence_penalty: 0, + max_tokens: 200, + stream: false, + n: 1, + }; + + console.log( + chalk.white("▲ ") + chalk.gray("Generating your AI commit message...") + ); + + const response = await fetch("https://api.openai.com/v1/completions", { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${OPENAI_API_KEY ?? ""}`, + }, + method: "POST", + body: JSON.stringify(payload), + }); + + const json = await response.json(); + const aiCommit = json.choices[0].text; + let cleanedUpAiCommit = aiCommit.replace(/(\r\n|\n|\r)/gm, ""); + + echo(cleanedUpAiCommit); + + let confirmationMessage = await question( + "\nWould you like to use this commit message? " + chalk.yellow("(Y/n) "), + { + choices: ["Y", "n"], + } + ); + + $.verbose = true; + echo("\n"); + + if (confirmationMessage !== "n") { + await $`git commit -m ${cleanedUpAiCommit}`; + } +})(); diff --git a/aicommit.mjs b/aicommit.mjs deleted file mode 100644 index 5bcd7ac..0000000 --- a/aicommit.mjs +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env zx -$.verbose = false; - -// TODO: Figure out how to fail gracefully instead of exit 1 - -let conventionalCommit = false; - -console.log(chalk.white("▲ ") + chalk.green("Welcome to AICommit!")); - -let pwd = await $`cd ~ && pwd;`; - -let { OPENAI_API_KEY } = await fs.readJson( - `${pwd.stdout.trim()}/ai-commit/.env.json` -); -let diff = await $`git diff --cached`; - -// Accounting for GPT-3's input req of 4k tokens (approx 8k chars) -if (diff.stdout.length > 8000) { - console.log("The diff is too large to write a commit message."); - await $`exit 1`; -} - -if (diff.stdout.length === 0) { - console.log( - "No staged changes found. Make sure there are changes and run `git add .`" - ); - await $`exit 1`; -} - -// 2371 chars = 1132 tokens = 60 LOC -// limit of 4k tokens = 8k characters -// 200 lines of code - -let prompt = `I want you to act like a git commit message writer. I will input a git diff and your job is to convert it into a useful commit message. ${ - conventionalCommit - ? "Preface the commit with 'feat:' if it is a feature or 'fix:' if it is a bug." - : "Do not preface the commit with anything." -} Return a complete sentence and do not repeat yourself: ${diff}`; - -const payload = { - model: "text-davinci-003", - prompt, - temperature: 0.7, - top_p: 1, - frequency_penalty: 0, - presence_penalty: 0, - max_tokens: 200, - stream: false, - n: 1, -}; - -console.log( - chalk.white("▲ ") + chalk.gray("Generating your AI commit message...") -); - -const response = await fetch("https://api.openai.com/v1/completions", { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${OPENAI_API_KEY ?? ""}`, - }, - method: "POST", - body: JSON.stringify(payload), -}); - -const json = await response.json(); -const aiCommit = json.choices[0].text; -let cleanedUpAiCommit = aiCommit.replace(/(\r\n|\n|\r)/gm, ""); - -echo(cleanedUpAiCommit); - -let confirmationMessage = await question( - "\nWould you like to use this commit message? " + chalk.yellow("(Y/n) "), - { - choices: ["Y", "n"], - } -); - -$.verbose = true; -echo("\n"); - -if (confirmationMessage !== "n") { - await $`git commit -m ${cleanedUpAiCommit}`; -} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f6283a1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14 @@ +{ + "name": "ai-commit", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "ai-commit", + "version": "1.0.0", + "hasInstallScript": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..24eb88a --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "ai-commit", + "version": "1.0.0", + "description": "generates ai commit", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "preinstall": "npm install -g zx" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Nutlope/ai-commit.git" + }, + "author": "hassan", + "license": "ISC", + "bugs": { + "url": "https://github.com/Nutlope/ai-commit/issues" + }, + "homepage": "https://github.com/Nutlope/ai-commit#readme" +}