feat: show multiple options (#64)
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
"@types/ini": "^1.3.31",
|
"@types/ini": "^1.3.31",
|
||||||
"@types/inquirer": "^9.0.3",
|
"@types/inquirer": "^9.0.3",
|
||||||
"@types/node": "^18.13.0",
|
"@types/node": "^18.13.0",
|
||||||
|
"cleye": "^1.3.2",
|
||||||
"eslint": "^8.34.0",
|
"eslint": "^8.34.0",
|
||||||
"execa": "^7.0.0",
|
"execa": "^7.0.0",
|
||||||
"ini": "^3.0.1",
|
"ini": "^3.0.1",
|
||||||
|
|||||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -6,6 +6,7 @@ specifiers:
|
|||||||
'@types/ini': ^1.3.31
|
'@types/ini': ^1.3.31
|
||||||
'@types/inquirer': ^9.0.3
|
'@types/inquirer': ^9.0.3
|
||||||
'@types/node': ^18.13.0
|
'@types/node': ^18.13.0
|
||||||
|
cleye: ^1.3.2
|
||||||
eslint: ^8.34.0
|
eslint: ^8.34.0
|
||||||
execa: ^7.0.0
|
execa: ^7.0.0
|
||||||
ini: ^3.0.1
|
ini: ^3.0.1
|
||||||
@@ -22,6 +23,7 @@ devDependencies:
|
|||||||
'@types/ini': 1.3.31
|
'@types/ini': 1.3.31
|
||||||
'@types/inquirer': 9.0.3
|
'@types/inquirer': 9.0.3
|
||||||
'@types/node': 18.13.0
|
'@types/node': 18.13.0
|
||||||
|
cleye: 1.3.2
|
||||||
eslint: 8.34.0
|
eslint: 8.34.0
|
||||||
execa: 7.0.0
|
execa: 7.0.0
|
||||||
ini: 3.0.1
|
ini: 3.0.1
|
||||||
@@ -891,6 +893,13 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/cleye/1.3.2:
|
||||||
|
resolution: {integrity: sha512-MngIC2izcCz07iRKr3Pe8Z6ZBv4zbKFl/YnQEN/aMHis6PpH+MxI2e6n0bMUAmSVlMoAyQkdBCSTbfDmtcSovQ==}
|
||||||
|
dependencies:
|
||||||
|
terminal-columns: 1.4.1
|
||||||
|
type-flag: 3.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/cli-cursor/3.1.0:
|
/cli-cursor/3.1.0:
|
||||||
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
|
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3178,6 +3187,10 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/terminal-columns/1.4.1:
|
||||||
|
resolution: {integrity: sha512-IKVL/itiMy947XWVv4IHV7a0KQXvKjj4ptbi7Ew9MPMcOLzkiQeyx3Gyvh62hKrfJ0RZc4M1nbhzjNM39Kyujw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/text-table/0.2.0:
|
/text-table/0.2.0:
|
||||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -3254,6 +3267,10 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/type-flag/3.0.0:
|
||||||
|
resolution: {integrity: sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/typed-array-length/1.0.4:
|
/typed-array-length/1.0.4:
|
||||||
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
|
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
61
src/cli.ts
61
src/cli.ts
@@ -1,10 +1,12 @@
|
|||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
import {
|
import {
|
||||||
black, green, red, bgCyan,
|
black, dim, green, red, bgCyan,
|
||||||
} from 'kolorist';
|
} from 'kolorist';
|
||||||
import {
|
import {
|
||||||
intro, outro, spinner, confirm, isCancel,
|
intro, outro, spinner, select, confirm, isCancel,
|
||||||
} from '@clack/prompts';
|
} from '@clack/prompts';
|
||||||
|
import { cli } from 'cleye';
|
||||||
|
import { description, version } from '../package.json';
|
||||||
import {
|
import {
|
||||||
getConfig,
|
getConfig,
|
||||||
assertGitRepo,
|
assertGitRepo,
|
||||||
@@ -13,6 +15,25 @@ import {
|
|||||||
generateCommitMessage,
|
generateCommitMessage,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
|
||||||
|
const argv = cli({
|
||||||
|
name: 'aicommits',
|
||||||
|
|
||||||
|
version,
|
||||||
|
|
||||||
|
flags: {
|
||||||
|
generate: {
|
||||||
|
type: Number,
|
||||||
|
description: 'Number of messages to generate. (Warning: generating multiple costs more)',
|
||||||
|
alias: 'g',
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
help: {
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
intro(bgCyan(black(' aicommits ')));
|
intro(bgCyan(black(' aicommits ')));
|
||||||
|
|
||||||
@@ -38,16 +59,36 @@ import {
|
|||||||
|
|
||||||
const s = spinner();
|
const s = spinner();
|
||||||
s.start('The AI is analyzing your changes');
|
s.start('The AI is analyzing your changes');
|
||||||
const message = await generateCommitMessage(OPENAI_KEY, staged.diff);
|
const messages = await generateCommitMessage(
|
||||||
s.stop('The commit message is ready for review');
|
OPENAI_KEY,
|
||||||
|
staged.diff,
|
||||||
|
argv.flags.generate,
|
||||||
|
);
|
||||||
|
s.stop('Changes analyzed');
|
||||||
|
|
||||||
const confirmed = await confirm({
|
let message;
|
||||||
message: `Would you like to commit with this message:\n\n ${message}\n`,
|
if (messages.length === 1) {
|
||||||
});
|
[message] = messages;
|
||||||
|
const confirmed = await confirm({
|
||||||
|
message: `Use this commit message?\n\n ${message}\n`,
|
||||||
|
});
|
||||||
|
|
||||||
if (!confirmed || isCancel(confirmed)) {
|
if (!confirmed || isCancel(confirmed)) {
|
||||||
outro('Commit cancelled');
|
outro('Commit cancelled');
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const selected = await select({
|
||||||
|
message: `Pick a commit message to use: ${dim('(Ctrl+c to exit)')}`,
|
||||||
|
options: messages.map(value => ({ label: value, value })),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isCancel(selected)) {
|
||||||
|
outro('Commit cancelled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
await execa('git', ['commit', '-m', message]);
|
await execa('git', ['commit', '-m', message]);
|
||||||
|
|||||||
@@ -60,11 +60,14 @@ export const getStagedDiff = async () => {
|
|||||||
|
|
||||||
export const getDetectedMessage = (files: string[]) => `Detected ${files.length.toLocaleString()} staged file${files.length > 1 ? 's' : ''}`;
|
export const getDetectedMessage = (files: string[]) => `Detected ${files.length.toLocaleString()} staged file${files.length > 1 ? 's' : ''}`;
|
||||||
|
|
||||||
|
const sanitizeMessage = (message: string) => message.trim().replace(/[\n\r]/g, '').replace(/(\w)\.$/, '$1');
|
||||||
|
|
||||||
const promptTemplate = 'Write an insightful but concise Git commit message in a complete sentence in present tense for the following diff without prefacing it with anything:';
|
const promptTemplate = 'Write an insightful but concise Git commit message in a complete sentence in present tense for the following diff without prefacing it with anything:';
|
||||||
|
|
||||||
export const generateCommitMessage = async (
|
export const generateCommitMessage = async (
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
diff: string,
|
diff: string,
|
||||||
|
completions: number,
|
||||||
) => {
|
) => {
|
||||||
const prompt = `${promptTemplate}\n${diff}`;
|
const prompt = `${promptTemplate}\n${diff}`;
|
||||||
|
|
||||||
@@ -84,10 +87,12 @@ export const generateCommitMessage = async (
|
|||||||
presence_penalty: 0,
|
presence_penalty: 0,
|
||||||
max_tokens: 200,
|
max_tokens: 200,
|
||||||
stream: false,
|
stream: false,
|
||||||
n: 1,
|
n: completions,
|
||||||
});
|
});
|
||||||
|
|
||||||
return completion.data.choices[0].text!.trim().replace(/[\n\r]/g, '').replace(/(\w)\.$/, '$1');
|
return completion.data.choices
|
||||||
|
.filter(choice => choice.text)
|
||||||
|
.map(choice => sanitizeMessage(choice.text!));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorAsAny = error as any;
|
const errorAsAny = error as any;
|
||||||
errorAsAny.message = `OpenAI API Error: ${errorAsAny.message} - ${errorAsAny.response.statusText}`;
|
errorAsAny.message = `OpenAI API Error: ${errorAsAny.message} - ${errorAsAny.response.statusText}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user