Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for longer commit messages #44

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ After doing the two steps above, generate your commit by running `aicommits`.

> Note: If you get a EACCESS error on mac/linux when running the first command, try running it with `sudo npm install -g aicommits`.

## Using the `--long` or `--verbose` Flag

By default, the AI Commits CLI generates concise commit messages. However, if you want to generate a longer commit message that provides more comprehensive information about the changes, you can use the --long or --verbose flags.

To use the --long or --verbose flag, simply add it to the end of the command:

```bash
aicommits --long
```

You can also use the short forms -l and -v:

```bash
aicommits -v
```

When the --long or --verbose flag is set, the AI Commits CLI will generate a more detailed commit message that provides comprehensive information about the changes. This can be useful when you want to provide more context for a particular commit.

## How it works

This CLI tool runs a `git diff` command to grab all the latest changes, sends this to OpenAI's GPT-3, then returns the AI generated commit message. I also want to note that it does cost money since GPT-3 generations aren't free. However, OpenAI gives folks $18 of free credits and commit message generations are cheap so it should be free for a long time.
Expand Down
222 changes: 121 additions & 101 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,108 +7,128 @@ import { Configuration, OpenAIApi } from 'openai';

const OPENAI_KEY = process.env.OPENAI_KEY ?? process.env.OPENAI_API_KEY;

const generateCommitMessage = async (
apiKey: string,
prompt: string,
) => {
const openai = new OpenAIApi(new Configuration({ apiKey }));
try {
const completion = await openai.createCompletion({
model: 'text-davinci-003',
prompt,
temperature: 0.7,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
max_tokens: 200,
stream: false,
n: 1,
});

return completion.data.choices[0].text!.trim().replace(/[\n\r]/g, '');
} catch (error) {
const errorAsAny = error as any;
errorAsAny.message = `OpenAI API Error: ${errorAsAny.message} - ${errorAsAny.response.statusText}`;
throw errorAsAny;
}
const generateCommitMessage = async (apiKey: string, prompt: string) => {
const openai = new OpenAIApi(new Configuration({ apiKey }));
try {
const completion = await openai.createCompletion({
model: 'text-davinci-003',
prompt,
temperature: 0.7,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
max_tokens: 200,
stream: false,
n: 1,
});

return completion.data.choices[0].text!.trim().replace(/[\n\r]/g, '');
} catch (error) {
const errorAsAny = error as any;
errorAsAny.message = `OpenAI API Error: ${errorAsAny.message} - ${errorAsAny.response.statusText}`;
throw errorAsAny;
}
};

(async () => {
console.log(chalk.white('▲ ') + chalk.green('Welcome to AICommits!'));

if (!OPENAI_KEY) {
console.error(
`${chalk.white('▲ ')
}Please save your OpenAI API key as an env variable by doing 'export OPENAI_KEY=YOUR_API_KEY'`,
);
process.exit(1);
}
try {
execSync('git rev-parse --is-inside-work-tree', {
encoding: 'utf8',
stdio: 'ignore',
});
} catch {
console.error(`${chalk.white('▲ ')}This is not a git repository`);
process.exit(1);
}

const diff = execSync(
'git diff --cached . ":(exclude)package-lock.json" ":(exclude)yarn.lock" ":(exclude)pnpm-lock.yaml"',
{
encoding: 'utf8',
},
);

if (!diff) {
console.log(
`${chalk.white('▲ ')
}No staged changes found. Make sure there are changes and run \`git add .\``,
);
process.exit(1);
}

// Accounting for GPT-3's input req of 4k tokens (approx 8k chars)
if (diff.length > 8000) {
console.log(
`${chalk.white('▲ ')}The diff is too large to write a commit message.`,
);
process.exit(1);
}

const 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. Do not preface the commit with anything, use the present tense, return a complete sentence, and do not repeat yourself: ${diff}`;

console.log(
chalk.white('▲ ') + chalk.gray('Generating your AI commit message...\n'),
);

try {
const aiCommitMessage = await generateCommitMessage(OPENAI_KEY, prompt);
console.log(
`${chalk.white('▲ ') + chalk.bold('Commit message: ') + aiCommitMessage
}\n`,
);

const confirmationMessage = await inquirer.prompt([
{
name: 'useCommitMessage',
message: 'Would you like to use this commit message? (Y / n)',
choices: ['Y', 'y', 'n'],
default: 'y',
},
]);

if (confirmationMessage.useCommitMessage === 'n') {
console.log(`${chalk.white('▲ ')}Commit message has not been commited.`);
process.exit(1);
}

execSync(`git commit -m "${aiCommitMessage}"`, {
stdio: 'inherit',
encoding: 'utf8',
});
} catch (error) {
console.error(chalk.white('▲ ') + chalk.red(error.message));
process.exit(1);
}
console.log(chalk.white('▲ ') + chalk.green('Welcome to AICommits!'));

if (!OPENAI_KEY) {
console.error(
`${chalk.white(
'▲ '
)}Please save your OpenAI API key as an env variable by doing 'export OPENAI_KEY=YOUR_API_KEY'`
);
process.exit(1);
}
try {
execSync('git rev-parse --is-inside-work-tree', {
encoding: 'utf8',
stdio: 'ignore',
});
} catch {
console.error(`${chalk.white('▲ ')}This is not a git repository`);
process.exit(1);
}

const diff = execSync(
'git diff --cached . ":(exclude)package-lock.json" ":(exclude)yarn.lock" ":(exclude)pnpm-lock.yaml"',
{
encoding: 'utf8',
}
);

if (!diff) {
console.log(
`${chalk.white(
'▲ '
)}No staged changes found. Make sure there are changes and run \`git add .\``
);
process.exit(1);
}

// Accounting for GPT-3's input req of 4k tokens (approx 8k chars)
if (diff.length > 8000) {
console.log(
`${chalk.white('▲ ')}The diff is too large to write a commit message.`
);
process.exit(1);
}

const CLI_ARGS = process.argv.slice(2);
let isLongArgSet = false;

let prompt = `Write an insightful but concise Git commit message in a complete sentence in present tense for the following diff without prefacing it with anything: ${diff}`;

if (
CLI_ARGS.includes('--long') ||
CLI_ARGS.includes('-l') ||
CLI_ARGS.includes('--verbose') ||
CLI_ARGS.includes('-v')
) {
isLongArgSet = true;
prompt = `Write a verbose Git commit message in a complete sentence in the present tense for the following diff without any preamble, providing with comprehensive information about the changes: ${diff}`;
}

console.log(
chalk.white('▲ ') +
chalk.gray(
`Generating your AI commit message ${
isLongArgSet
? 'with a verbose description'
: 'with a concise description'
}...\n`
)
);

try {
const aiCommitMessage = await generateCommitMessage(OPENAI_KEY, prompt);
console.log(
`${
chalk.white('▲ ') + chalk.bold('Commit message: ') + aiCommitMessage
}\n`
);

const confirmationMessage = await inquirer.prompt([
{
name: 'useCommitMessage',
message: 'Would you like to use this commit message? (Y / n)',
choices: ['Y', 'y', 'n'],
default: 'y',
},
]);

if (confirmationMessage.useCommitMessage === 'n') {
console.log(`${chalk.white('▲ ')}Commit message has not been commited.`);
process.exit(1);
}

execSync(`git commit -m "${aiCommitMessage}"`, {
stdio: 'inherit',
encoding: 'utf8',
});
} catch (error) {
console.error(chalk.white('▲ ') + chalk.red(error.message));
process.exit(1);
}
})();