Compare commits

..

101 Commits

Author SHA1 Message Date
di-sukharev
5f85cafc7e 3.0.5 2024-02-27 22:07:16 +08:00
di-sukharev
0591e6e81e docs(README.md): add alternative ways to run OpenCommit via npx and bunx
The README.md file has been updated to include alternative ways to run OpenCommit via the `npx opencommit` and `bunx opencommit` commands. This provides users with more options to execute the tool without the need for a global installation.
2024-02-27 22:06:08 +08:00
di-sukharev
a296892aaf build 2024-02-27 22:04:39 +08:00
di-sukharev
45958284c9 Merge branch 'master' into dev 2024-02-27 22:04:13 +08:00
Jaroslaw Weber
1d6980faf3 add ollama support (#269)
* add ollama support

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
Co-authored-by: GPT10 <57486732+di-sukharev@users.noreply.github.com>
Co-authored-by: Jaroslaw Weber <jaroslaw.weber@adriel.com>
2024-02-27 22:01:13 +08:00
di-sukharev
f793f01059 Merge branch 'master' of github.com:di-sukharev/opencommit 2023-11-27 12:14:13 +08:00
di-sukharev
7deffa8ee2 update twitter 2023-11-27 12:00:28 +08:00
GPT10
84dfc85328 Update README.md
fix(README.md): GitHub hackathon link
2023-10-05 22:05:45 +08:00
di-sukharev
b79aef5fad build 2023-09-18 14:23:43 +08:00
di-sukharev
57d9cc59b5 fix(config.ts): add default value for config parameter in OCO_OPENAI_API_KEY validator function
The OCO_OPENAI_API_KEY validator function now accepts an optional config parameter with a default value of an empty object {}. This change ensures that the function can be called without providing the config parameter, improving flexibility and avoiding potential errors when the parameter is not provided.
2023-09-18 14:21:55 +08:00
di-sukharev
e599700d72 build 2023-09-09 15:52:29 +08:00
di-sukharev
2761403735 Merge remote-tracking branch 'origin/master' into dev 2023-09-09 15:52:13 +08:00
di-sukharev
e57033c4a1 chore(README.md): update information about default model and cost
The default model used by OpenCommit is ChatGPT (3.5-turbo-16k) official model. It is mentioned that the cost should not exceed $0.10 per casual working day. Additionally, it is now mentioned that users have the option to switch to gpt-4, which is better but more expensive. This update provides users with more information about the available models and their associated costs.
2023-09-05 20:30:39 +08:00
di-sukharev
ca049e4b5d chore(package.json): update deploy script to push tags before publishing to npm
The deploy script in package.json has been updated to push tags before publishing to npm. This ensures that the latest version tag is pushed to the remote repository before publishing the package.
2023-09-05 13:57:20 +08:00
di-sukharev
2d48648f52 chore(package.json): update version to 3.0.3
The version in package.json has been updated from 3.0.2 to 3.0.3. This change is made to reflect the latest version of the project.
2023-09-05 13:51:21 +08:00
di-sukharev
40297e0c6a chore(package.json): update version to 3.0.2
The version in package.json has been updated from 3.0.1 to 3.0.2. This change is made to reflect the latest version of the project.
2023-09-05 13:49:17 +08:00
di-sukharev
75f0cd47b8 chore(package.json): update version to 3.0.1
The version in package.json has been updated from 3.0.0 to 3.0.1. This change is made to reflect the new version of the project.
2023-09-05 13:48:09 +08:00
Sukharev
c76313737d #247: fix (#248) 2023-09-05 13:45:34 +08:00
di-sukharev
a2b1890e7e build 2023-09-05 13:42:30 +08:00
di-sukharev
df705b97b7 Merge remote-tracking branch 'origin/master' into dev 2023-09-05 13:42:22 +08:00
di-sukharev
df280b7db7 fix(commitlint): use process.cwd() as fallback for process.env.PWD to ensure compatibility across platforms
The commitlint module now uses process.cwd() as a fallback when process.env.PWD is not available. This ensures compatibility across different platforms and environments when determining the path to the node_modules directory.
2023-09-05 13:39:49 +08:00
Sukharev
67dff60a7d chore(prompts.ts): update INIT_MAIN_PROMPT content to provide clearer instructions and improve readability (#245) 2023-09-03 15:39:52 +08:00
di-sukharev
08fb4d801f chore: add .npmignore file and update package.json to include specific files in the "files" field
The .npmignore file is added to the project. It currently includes the file "out/github-action.cjs" to be ignored when publishing the package to npm.

In the package.json file, the "files" field is updated to include the files "out/cli.cjs" and "out/tiktoken_bg.wasm". This ensures that these files are included when publishing the package to npm.

The "deploy" script in the package.json file is also updated to include pushing tags to the remote repository using "git push --tags". This ensures that the newly created version tags are pushed along with the code changes.
2023-09-03 15:29:29 +08:00
di-sukharev
ac8c87be9e docs(README.md): add note about MacOS potentially requiring 'sudo' when installing package globally
The README.md file now includes a note that MacOS may prompt the user to run the 'npm install -g opencommit' command with 'sudo' when installing the package globally. This note is added to provide clarity and help users who encounter this situation.
2023-09-03 15:22:47 +08:00
di-sukharev
a9050fda39 build 2023-09-03 15:17:12 +08:00
Sukharev
b98b892ba1 v3.0.0 (#243)
* update major version

---------

Co-authored-by: E Einowski <5124085+EwiththeBowtie@users.noreply.github.com>
Co-authored-by: Max Zavodniuk <lovemaxonly@gmail.com>
Co-authored-by: Malthe Poulsen <30603252+malpou@users.noreply.github.com>
Co-authored-by: Sébastien Fichot <fichot.sebastien@gmail.com>
2023-09-03 15:16:02 +08:00
di-sukharev
0460a252e2 update major version 2023-09-03 15:11:14 +08:00
di-sukharev
ebeb68fd9b chore(package.json): update deploy script to use 'git push --follow-tags' instead of 'git push --tags'
The deploy script in package.json has been updated to use 'git push --follow-tags' instead of 'git push --tags'. This change ensures that tags are pushed along with the commit when deploying the application.
2023-09-03 15:07:43 +08:00
di-sukharev
a9f550fb79 Merge remote-tracking branch 'origin/master' into dev 2023-09-03 15:04:09 +08:00
Sukharev
4181c0b20d Refactoring and cleaning for v3 (#244)
* chore(README.md): update setup instructions for OpenCommit as a GitHub Action
fix(commit.ts): check if OCO_MESSAGE_TEMPLATE_PLACEHOLDER exists before replacing it in the commit message template
The README.md file has been updated to reflect the changes in the setup instructions for using OpenCommit as a GitHub Action. The new instructions provide a file template for the GitHub Action workflow and include the latest version of the OpenCommit GitHub Action.

In the commit.ts file, a fix has been made to check if the OCO_MESSAGE_TEMPLATE_PLACEHOLDER exists before replacing it in the commit message template. This ensures that the replacement is only performed when the placeholder is present, preventing any potential errors.
2023-09-03 14:59:37 +08:00
di-sukharev
765e9884dd chore(package.json): reorder scripts to improve readability and maintainability 2023-09-03 14:10:56 +08:00
di-sukharev
ea9411fa69 build 2023-09-03 14:09:45 +08:00
Sébastien Fichot
12956d7633 feat: Integrate @commitlint for Enhanced Commit Message Generation and Configuration Support (#209)
* add commitlint support
* refactor code
* improve readme text
2023-09-03 14:00:18 +08:00
Malthe Poulsen
c1627bb98c 🔧 chore(github-action.ts): add check for changes in commit messages before rebasing to improve efficiency (#222)
🐛 fix(github-action.ts): fix logic to check if there are any changes in commit messages before rebasing to ensure correct behavior
2023-09-03 12:22:05 +08:00
Max Zavodniuk
4e374aa9db fix(config.ts): pass config object to configValidators[CONFIG_KEYS.OCO_OPENAI_API_KEY] to allow access to other config values (#225)
* any length `OCO_OPENAI_API_KEY` if `OCO_OPENAI_BASE_PATH` is set
2023-09-03 12:21:13 +08:00
E Einowski
ef003bdad6 Add Detailed Explanation of OCO_MESSAGE_TEMPLATE_PLACEHOLDER Feature to README.md (#242)
* 📝 docs(README.md): add detailed explanation of OCO_MESSAGE_TEMPLATE_PLACEHOLDER feature
2023-09-03 12:18:10 +08:00
Sukharev
ba5dd848b8 Update README.md 2023-08-24 12:00:43 +08:00
di-sukharev
916ddf02d1 2.4.2 2023-07-05 15:20:11 +08:00
di-sukharev
fa85ec0351 build 2023-07-05 15:20:03 +08:00
di-sukharev
3a7f2d69e7 2.4.1 2023-07-05 15:19:29 +08:00
di-sukharev
f240d35231 build 2023-07-05 15:19:20 +08:00
di-sukharev
04ee73ca9d 2.4.0 2023-07-05 15:18:19 +08:00
di-sukharev
91885cdd1c 2.3.0 2023-07-05 15:18:02 +08:00
di-sukharev
b7a2cd46b3 build 2023-07-05 15:13:58 +08:00
di-sukharev
028c0bc518 format files 2023-07-05 15:13:30 +08:00
di-sukharev
897eb73cd7 feat(README.md): add GitHub Hackathon winner badge to project description
docs(README.md): restructure README, move CLI setup instructions to top for better visibility
feat(cli.cjs): extend file exclusion list in 'getDiff' to include image files for better performance
refactor(config.ts): add more model options in configValidators for flexibility
refactor(config.ts): set default model to 'gpt-3.5-turbo-16k' for better performance
refactor(git.ts): simplify 'getCoreHooksPath' function for readability
feat(git.ts): extend file exclusion list in 'getDiff' to include image files for better performance
2023-07-05 15:11:17 +08:00
di-sukharev
e7ce40a8d1 build 2023-07-05 14:57:03 +08:00
di-sukharev
18d25672d7 docs(CONTRIBUTING.md): clarify pull request target branch in contribution guidelines
This change emphasizes that pull requests should be made to the `dev` branch, not the `master` branch, to ensure proper workflow.
2023-07-05 14:52:09 +08:00
di-sukharev
79b1008e02 Merge branch 'dev' 2023-07-05 14:50:19 +08:00
seho
3c0a271bf8 feat(config.ts): add OCO_MESSAGE_TEMPLATE_PLACEHOLDER configuration item to allow users to customize the message template placeholder (#208)
feat(commit.ts): add check for message templates in extraArgs and replace OCO_MESSAGE_TEMPLATE_PLACEHOLDER with generated commit message if found
docs(README.md): add documentation for OCO_MESSAGE_TEMPLATE_PLACEHOLDER configuration item and how to use it in the command line (#205)
2023-07-05 14:27:43 +08:00
Sam Rudloff
ccfd24a9e5 Adding gpt-3.5-turbo-16k Modal (#215) 2023-07-05 14:25:21 +08:00
Avighna
0c8bf5562d Fixed lots of changes (#195) 2023-06-19 18:34:58 +08:00
Sukharev
96c7676a13 Revert "Fixing grammatical errors and enhancing overall grammar in README.md (#190)" (#210)
This reverts commit 90f64d5475.
2023-06-19 18:33:39 +08:00
Avighna
90f64d5475 Fixing grammatical errors and enhancing overall grammar in README.md (#190)
* Fixed grammatical errors and enhanced overall grammar in README.md
2023-06-19 18:32:12 +08:00
di-sukharev
a70b831f9d build 2023-05-26 13:14:47 +08:00
di-sukharev
cad179953a 2.2.10 2023-05-26 13:14:42 +08:00
di-sukharev
8979841010 refactor(api.ts): reformat imports to be grouped and sorted alphabetically
refactor(api.ts): replace axios with execa to get the latest version of opencommit from npm registry
2023-05-26 13:14:33 +08:00
di-sukharev
1e974086d3 build 2023-05-26 13:12:07 +08:00
di-sukharev
792ab67ef1 2.2.9 2023-05-26 13:12:00 +08:00
di-sukharev
a7af55df37 feat(checkIsLatestVersion.ts): add outro message from @clack/prompts to warn user about not using the latest stable version of OpenCommit 2023-05-26 13:11:51 +08:00
di-sukharev
5c540abae9 build 2023-05-26 13:10:41 +08:00
di-sukharev
f69e716dcc 2.2.8 2023-05-26 13:10:29 +08:00
di-sukharev
1d8d8e57c2 style(.prettierrc): reorder properties to follow alphabetical order 2023-05-26 13:09:53 +08:00
di-sukharev
fdc638cd86 build 2023-05-26 13:08:19 +08:00
di-sukharev
56e02f2604 2.2.7 2023-05-26 13:08:08 +08:00
di-sukharev
b926a627a8 Merge remote-tracking branch 'origin/dev' 2023-05-26 13:07:42 +08:00
Gabriel Moreli
32f3e176f0 feat(api.ts): solving bad request issue (#187)
* 2.0.18

* patch

* 2.0.19

* style(.prettierrc): reorder properties to follow alphabetical order and improve readability

* feat(generateCommitMessageFromGitDiff.ts): changing logic of MAX_REQ_TOKENS

fix(api.ts): add missing import for GenerateCommitMessageErrorEnum
The token count validation is added to prevent the request from exceeding the default model token limit. The validation is done by counting the tokens in each message and adding 4 to each count to account for the additional tokens added by the API. If the total token count exceeds the limit, an error is thrown. The missing import for GenerateCommitMessageErrorEnum is also added.

feat: add support for splitting long line-diffs into smaller pieces
This change adds support for splitting long line-diffs into smaller pieces to avoid exceeding the maximum commit message length. The `splitDiff` function splits a single line into multiple lines if it exceeds the maximum length. It also splits the diff into smaller pieces if adding the next line would exceed the maximum length. This change improves the readability of commit messages and makes them more consistent.

refactor: improve code readability by adding whitespace and reformatting code
This commit improves the readability of the code by adding whitespace and reformatting the code. The changes do not affect the functionality of the code. Additionally, a new function `delay` has been added to the file.

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2023-05-26 13:07:09 +08:00
di-sukharev
cf4212016f build 2023-05-26 12:57:44 +08:00
di-sukharev
c491fa4bad 2.2.6 2023-05-26 12:57:38 +08:00
di-sukharev
f10fc37fe7 refactor(CommandsEnum.ts): reorder COMMANDS enum values to improve readability and maintainability 2023-05-26 12:57:20 +08:00
di-sukharev
f7b1a6358f build 2023-05-26 12:55:27 +08:00
di-sukharev
5f9f29c467 2.2.5 2023-05-26 12:55:14 +08:00
di-sukharev
0ec5dab80a 2.2.4 2023-05-26 12:54:52 +08:00
di-sukharev
8d47a1bb0f chore(package.json): revert version to 2.2.3 and remove postversion script as it is no longer needed
feat(package.json): add deploy script to automate versioning, building, pushing and publishing the package to npm registry
2023-05-26 12:54:42 +08:00
di-sukharev
fad05e0757 2.2.4 2023-05-26 12:52:00 +08:00
di-sukharev
12f7e7eaf9 build 2023-05-26 12:51:55 +08:00
di-sukharev
6490532818 2.2.3 2023-05-26 12:43:53 +08:00
di-sukharev
e0e953dab8 build 2023-05-26 12:43:49 +08:00
di-sukharev
75fa04efd4 2.2.2 2023-05-26 12:42:47 +08:00
di-sukharev
93019139fb build 2023-05-26 12:42:43 +08:00
di-sukharev
ce47c1fe81 2.2.1 2023-05-26 12:37:20 +08:00
di-sukharev
a880dd6bd2 build 2023-05-26 12:37:16 +08:00
di-sukharev
f193bb1d96 chore(package.json): update version from 2.1.4 to 2.2.0 to reflect new features and improvements 2023-05-26 12:35:11 +08:00
di-sukharev
4cc34dfa5f chore(package.json): update version from 2.1.3 to 2.1.4 to reflect recent changes and improvements 2023-05-26 12:33:18 +08:00
di-sukharev
d034238505 chore(package.json): update version from 2.1.2 to 2.1.3 to reflect recent changes and improvements 2023-05-26 12:27:48 +08:00
di-sukharev
25acb1c219 chore(package.json): update version from 2.1.1 to 2.1.2 to reflect recent changes and improvements 2023-05-26 12:27:28 +08:00
di-sukharev
44c9d48eae 2.1.1 2023-05-26 12:22:15 +08:00
di-sukharev
702eab9ec0 build 2023-05-26 12:22:11 +08:00
di-sukharev
e8be3858ab update v to 2.1.0 2023-05-26 12:22:07 +08:00
di-sukharev
50e7cd3576 chore(package.json): update package version from 2.0.19 to 2.1.0
refactor(cli.ts): uncomment checkIsLatestVersion function call to ensure the latest version of the package is being used
2023-05-26 12:16:56 +08:00
di-sukharev
4f57201e98 build 2023-05-26 12:09:25 +08:00
di-sukharev
009462f92c Merge remote-tracking branch 'origin/master' into dev 2023-05-26 12:06:00 +08:00
Moulick Aggarwal
34775e9e69 Remove oc alias (#178)
oc is pretty wellknow alias for OpenShift CLI

Signed-off-by: Moulick Aggarwal <moulickaggarwal@gmail.com>
2023-05-26 12:05:18 +08:00
Hiro
3f3043c48e doc: add timeout-minutes to workflow (#186)
* 2.0.18

* patch

* 2.0.19

* style(.prettierrc): reorder properties to follow alphabetical order and improve readability

* doc: add timeout-minutes to workflow

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2023-05-26 11:55:31 +08:00
di-sukharev
820422760a style(.prettierrc): reorder properties to follow alphabetical order and improve readability 2023-05-24 13:42:42 +08:00
di-sukharev
b40657c1a7 2.0.19 2023-05-22 12:32:23 +08:00
di-sukharev
dcd8f52be3 patch 2023-05-22 12:32:12 +08:00
di-sukharev
0eff198eb9 2.0.18 2023-05-22 12:31:43 +08:00
di-sukharev
b345eee815 build 2023-05-22 12:31:19 +08:00
henrycunh
cba599337d refactor(config.ts): handle undefined values and improve validation for OCO_OPENAI_MAX_TOKENS and OCO_MODEL (#176)
fix(config.ts): set default values for OCO_MODEL and OCO_LANGUAGE when reading from environment variables
refactor(generateCommitMessageFromGitDiff.ts): remove unused GenerateCommitMessageError interface
2023-05-22 12:30:41 +08:00
di-sukharev
4e25f1460a chore(README.md): update opencommit action version to v1.0.4 to fix bugs and improve functionality 2023-05-22 12:27:58 +08:00
37 changed files with 8385 additions and 5656 deletions

View File

@@ -9,7 +9,7 @@ Thanks for considering contributing to the project.
3. Create a new branch for your changes.
4. Make your changes and commit them with descriptive commit messages.
5. Push your changes to your forked repository.
6. Create a pull request from your branch to the `dev` branch.
6. Create a pull request from your branch to the `dev` branch. Not `master` branch, PR to `dev` branch, please.
## Getting started

BIN
.github/github-mark-white.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

1
.npmignore Normal file
View File

@@ -0,0 +1 @@
out/github-action.cjs

1
.opencommitignore Normal file
View File

@@ -0,0 +1 @@
out

452
README.md
View File

@@ -2,12 +2,12 @@
<div>
<img src=".github/logo-grad.svg" alt="OpenCommit logo"/>
<h1 align="center">OpenCommit</h1>
<h4 align="center">Follow the bird <a href="https://twitter.com/io_Y_oi"><img src="https://img.shields.io/twitter/follow/io_Y_oi?style=flat&label=io_Y_oi&logo=twitter&color=0bf&logoColor=fff" align="center"></a>
</h4>
<h4 align="center">Follow the bird <a href="https://twitter.com/_sukharev_"><img src="https://img.shields.io/twitter/follow/_sukharev_?style=flat&label=_sukharev_&logo=twitter&color=0bf&logoColor=fff" align="center"></a>
</div>
<h2>Auto-generate meaningful commits in 1 second</h2>
<p>Killing lame commits with AI 🤯🔫</p>
<a href="https://www.npmjs.com/package/opencommit"><img src="https://img.shields.io/npm/v/opencommit" alt="Current version"></a>
<h4 align="center"><a href="https://twitter.com/_sukharev_/status/1683448136973582336">🪩 Winner of GitHub 2023 HACKATHON 🪩</a></h4>
</div>
---
@@ -16,15 +16,278 @@
<img src=".github/opencommit-example.png" alt="OpenCommit example"/>
</div>
All the commits in this repo are done with OpenCommit — look into [the commits](https://github.com/di-sukharev/opencommit/commit/eae7618d575ee8d2e9fff5de56da79d40c4bc5fc) to see how OpenCommit works. Emoji and long commit description text is configurable.
All the commits in this repo are authored by OpenCommit — look at [the commits](https://github.com/di-sukharev/opencommit/commit/eae7618d575ee8d2e9fff5de56da79d40c4bc5fc) to see how OpenCommit works. Emojis and long commit descriptions are configurable.
## Setup OpenCommit as a Github Action 🔥
## Setup OpenCommit as a CLI tool
You can use OpenCommit by simply running it via the CLI like this `oco`. 2 seconds and your staged changes are committed with a meaningful message.
1. Install OpenCommit globally to use in any repository:
```sh
npm install -g opencommit
```
Alternatively run it via `npx opencommit` or `bunx opencommit`
MacOS may ask to run the command with `sudo` when installing a package globally.
2. Get your API key from [OpenAI](https://platform.openai.com/account/api-keys). Make sure that you add your payment details, so the API works.
3. Set the key to OpenCommit config:
```sh
oco config set OCO_OPENAI_API_KEY=<your_api_key>
```
Your API key is stored locally in the `~/.opencommit` config file.
## Usage
You can call OpenCommit directly to generate a commit message for your staged changes:
```sh
git add <files...>
opencommit
```
You can also use the `oco` shortcut:
```sh
git add <files...>
oco
```
You can also run it with local model through ollama:
- install and start ollama
- run `ollama run mistral` (do this only once, to pull model)
- run (in your project directory):
```sh
git add <files...>
AI_PROVIDER='ollama' opencommit
```
## Configuration
### Local per repo configuration
Create a `.env` file and add OpenCommit config variables there like this:
```env
OCO_OPENAI_API_KEY=<your OpenAI API token>
OCO_OPENAI_MAX_TOKENS=<max response tokens from OpenAI API>
OCO_OPENAI_BASE_PATH=<may be used to set proxy path to OpenAI api>
OCO_DESCRIPTION=<postface a message with ~3 sentences description of the changes>
OCO_EMOJI=<boolean, add GitMoji>
OCO_MODEL=<either 'gpt-4', 'gpt-3.5-turbo-16k' (default), 'gpt-3.5-turbo-0613' or 'gpt-3.5-turbo'>
OCO_LANGUAGE=<locale, scroll to the bottom to see options>
OCO_MESSAGE_TEMPLATE_PLACEHOLDER=<message template placeholder, default: '$msg'>
OCO_PROMPT_MODULE=<either conventional-commit or @commitlint, default: conventional-commit>
```
### Global config for all repos
Local config still has more priority than Global config, but you may set `OCO_MODEL` and `OCO_LOCALE` globally and set local configs for `OCO_EMOJI` and `OCO_DESCRIPTION` per repo which is more convenient.
Simply set any of the variables above like this:
```sh
oco config set OCO_MODEL=gpt-4
```
Configure [GitMoji](https://gitmoji.dev/) to preface a message.
```sh
oco config set OCO_EMOJI=true
```
To remove preface emojis:
```sh
oco config set OCO_EMOJI=false
```
### Switch to GPT-4 or other models
By default, OpenCommit uses `gpt-3.5-turbo-16k` model.
You may switch to GPT-4 which performs better, but costs ~x15 times more 🤠
```sh
oco config set OCO_MODEL=gpt-4
```
or for as a cheaper option:
```sh
oco config set OCO_MODEL=gpt-3.5-turbo
```
Make sure that you spell it `gpt-4` (lowercase) and that you have API access to the 4th model. Even if you have ChatGPT+, that doesn't necessarily mean that you have API access to GPT-4.
### Locale configuration
To globally specify the language used to generate commit messages:
```sh
# de, German ,Deutsch
oco config set OCO_LANGUAGE=de
oco config set OCO_LANGUAGE=German
oco config set OCO_LANGUAGE=Deutsch
# fr, French, française
oco config set OCO_LANGUAGE=fr
oco config set OCO_LANGUAGE=French
oco config set OCO_LANGUAGE=française
```
The default language setting is **English**
All available languages are currently listed in the [i18n](https://github.com/di-sukharev/opencommit/tree/master/src/i18n) folder
### Switch to `@commitlint`
OpenCommit allows you to choose the prompt module used to generate commit messages. By default, OpenCommit uses its conventional-commit message generator. However, you can switch to using the `@commitlint` prompt module if you prefer. This option lets you generate commit messages in respect with the local config.
You can set this option by running the following command:
```sh
oco config set OCO_PROMPT_MODULE=<module>
```
Replace `<module>` with either `conventional-commit` or `@commitlint`.
#### Example:
To switch to using th` '@commitlint` prompt module, run:
```sh
oco config set OCO_PROMPT_MODULE=@commitlint
```
To switch back to the default conventional-commit message generator, run:
```sh
oco config set OCO_PROMPT_MODULE=conventional-commit
```
#### Integrating with `@commitlint`
The integration between `@commitlint` and OpenCommit is done automatically the first time OpenCommit is run with `OCO_PROMPT_MODULE` set to `@commitlint`. However, if you need to force set or reset the configuration for `@commitlint`, you can run the following command:
```sh
oco commitlint force
```
To view the generated configuration for `@commitlint`, you can use this command:
```sh
oco commitlint get
```
This allows you to ensure that the configuration is set up as desired.
Additionally, the integration creates a file named `.opencommit-commitlint` which contains the prompts used for the local `@commitlint` configuration. You can modify this file to fine-tune the example commit message generated by OpenAI. This gives you the flexibility to make adjustments based on your preferences or project guidelines.
OpenCommit generates a file named `.opencommit-commitlint` in your project directory which contains the prompts used for the local `@commitlint` configuration. You can modify this file to fine-tune the example commit message generated by OpenAI. If the local `@commitlint` configuration changes, this file will be updated the next time OpenCommit is run.
This offers you greater control over the generated commit messages, allowing for customization that aligns with your project's conventions.
## Git flags
The `opencommit` or `oco` commands can be used in place of the `git commit -m "${generatedMessage}"` command. This means that any regular flags that are used with the `git commit` command will also be applied when using `opencommit` or `oco`.
```sh
oco --no-verify
```
is translated to :
```sh
git commit -m "${generatedMessage}" --no-verify
```
To include a message in the generated message, you can utilize the template function, for instance:
```sh
oco '#205: $msg
```
> opencommit examines placeholders in the parameters, allowing you to append additional information before and after the placeholders, such as the relevant Issue or Pull Request. Similarly, you have the option to customize the OCO_MESSAGE_TEMPLATE_PLACEHOLDER configuration item, for example, simplifying it to $m!"
### Message Template Placeholder Config
#### Overview
The `OCO_MESSAGE_TEMPLATE_PLACEHOLDER` feature in the `opencommit` tool allows users to embed a custom message within the generated commit message using a template function. This configuration is designed to enhance the flexibility and customizability of commit messages, making it easier for users to include relevant information directly within their commits.
#### Implementation Details
In our codebase, the implementation of this feature can be found in the following segment:
```javascript
commitMessage = messageTemplate.replace(
config?.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
commitMessage
);
```
This line is responsible for replacing the placeholder in the `messageTemplate` with the actual `commitMessage`.
#### Usage
For instance, using the command `oco '$msg #205`, users can leverage this feature. The provided code represents the backend mechanics of such commands, ensuring that the placeholder is replaced with the appropriate commit message.
#### Committing with the Message
Once users have generated their desired commit message, they can proceed to commit using the generated message. By understanding the feature's full potential and its implementation details, users can confidently use the generated messages for their commits.
### Ignore files
You can remove files from being sent to OpenAI by creating a `.opencommitignore` file. For example:
```ignorelang
path/to/large-asset.zip
**/*.jpg
```
This helps prevent opencommit from uploading artifacts and large files.
By default, opencommit ignores files matching: `*-lock.*` and `*.lock`
## Git hook (KILLER FEATURE)
You can set OpenCommit as Git [`prepare-commit-msg`](https://git-scm.com/docs/githooks#_prepare_commit_msg) hook. Hook integrates with your IDE Source Control and allows you to edit the message before committing.
To set the hook:
```sh
oco hook set
```
To unset the hook:
```sh
oco hook unset
```
To use the hook:
```sh
git add <files...>
git commit
```
Or follow the process of your IDE Source Control feature, when it calls `git commit` command — OpenCommit will integrate into the flow.
## Setup OpenCommit as a GitHub Action (BETA) 🔥
OpenCommit is now available as a GitHub Action which automatically improves all new commits messages when you push to remote!
This is great if you want to make sure all of the commits in all of repository branches are meaningful and not lame like `fix1` or `done2`.
This is great if you want to make sure all of the commits in all of your repository branches are meaningful and not lame like `fix1` or `done2`.
Create a file `.github/workflows/opencommit.yml` with contents below:
Create a file `.github/workflows/opencommit.yml` with the contents below:
```yml
name: 'OpenCommit Action'
@@ -37,6 +300,7 @@ on:
jobs:
opencommit:
timeout-minutes: 10
name: OpenCommit
runs-on: ubuntu-latest
permissions: write-all
@@ -48,7 +312,7 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: di-sukharev/opencommit@github-action-v1.0.1
- uses: di-sukharev/opencommit@github-action-v1.0.4
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -63,173 +327,23 @@ jobs:
OCO_OPENAI_BASE_PATH: ''
OCO_DESCRIPTION: false
OCO_EMOJI: false
OCO_MODEL: gpt-3.5-turbo
OCO_MODEL: gpt-3.5-turbo-16k
OCO_LANGUAGE: en
OCO_PROMPT_MODULE: conventional-commit
```
That is it. Now when you push to any branch in your repo — all NEW commits are being improved by never-tired-AI.
That is it. Now when you push to any branch in your repo — all NEW commits are being improved by your never-tired AI.
Make sure you exclude public collaboration branches (`main`, `dev`, `etc`) in `branches-ignore`, so OpenCommit does not rebase commits there when improving the messages.
Make sure you exclude public collaboration branches (`main`, `dev`, `etc`) in `branches-ignore`, so OpenCommit does not rebase commits there while improving the messages.
Interactive rebase (`rebase -i`) changes commit SHA, so commit history in remote becomes different with your local branch history. It's ok when you work on the branch alone, but may be inconvenient for other collaborators.
## Setup OpenCommit as a CLI
You can use OpenCommit by simply running it via CLI like this `oc`. 2 seconds and your staged changes are committed with a meaningful message.
1. Install OpenCommit globally to use in any repository:
```sh
npm install -g opencommit
```
2. Get your API key from [OpenAI](https://platform.openai.com/account/api-keys). Make sure you add payment details, so API works.
3. Set the key to OpenCommit config:
```sh
opencommit config set OCO_OPENAI_API_KEY=<your_api_key>
```
Your api key is stored locally in `~/.opencommit` config file.
## Usage
You can call OpenCommit directly to generate a commit message for your staged changes:
```sh
git add <files...>
opencommit
```
You can also use the `oc` shortcut:
```sh
git add <files...>
oc
```
## Configuration
### Local per repo configuration
Create an `.env` file and add OpenCommit config variables there like this:
```env
OCO_OPENAI_API_KEY=<your openAI API token>
OCO_OPENAI_MAX_TOKENS=<max response tokens from openAI API>
OCO_OPENAI_BASE_PATH=<may be used to set proxy path to openAI api>
OCO_DESCRIPTION=<postface a message with ~3 sentences description>
OCO_EMOJI=<add GitMoji>
OCO_MODEL=<either gpt-3.5-turbo or gpt-4>
OCO_LANGUAGE=<locale, scroll to the bottom to see options>
```
### Global config for all repos
Local config still has more priority as Global config, but you may set `OCO_MODEL` and `OCO_LOCALE` globally and set local configs for `OCO_EMOJI` and `OCO_DESCRIPTION` per repo which is more convenient.
Simply run any of the variable above like this:
```sh
oc config set OCO_OPENAI_API_KEY=gpt-4
```
Configure [GitMoji](https://gitmoji.dev/) to preface a message.
```sh
oc config set OCO_EMOJI=true
```
To remove preface emoji:
```sh
oc config set OCO_EMOJI=false
```
### Switch to GPT-4
By default OpenCommit uses GPT-3.5-turbo (ChatGPT).
You may switch to GPT-4 which performs better, but costs ~x15 times more 🤠
```sh
oc config set OCO_MODEL=gpt-4
```
Make sure you do lowercase `gpt-4` and you have API access to the 4th model. Even if you have ChatGPT+ it doesn't necessarily mean that you have API access to GPT-4.
## Locale configuration
To globally specify the language used to generate commit messages:
```sh
# de, German ,Deutsch
oc config set OCO_LANGUAGE=de
oc config set OCO_LANGUAGE=German
oc config set OCO_LANGUAGE=Deutsch
# fr, French, française
oc config set OCO_LANGUAGE=fr
oc config set OCO_LANGUAGE=French
oc config set OCO_LANGUAGE=française
```
The default language set is **English**
All available languages are currently listed in the [i18n](https://github.com/di-sukharev/opencommit/tree/master/src/i18n) folder
### Git flags
The `opencommit` or `oc` commands can be used in place of the `git commit -m "${generatedMessage}"` command. This means that any regular flags that are used with the `git commit` command will also be applied when using `opencommit` or `oc`.
```sh
oc --no-verify
```
is translated to :
```sh
git commit -m "${generatedMessage}" --no-verify
```
### Ignore files
You can ignore files from submission to OpenAI by creating a `.opencommitignore` file. For example:
```ignorelang
path/to/large-asset.zip
**/*.jpg
```
This is useful for preventing opencommit from uploading artifacts and large files.
By default, opencommit ignores files matching: `*-lock.*` and `*.lock`
## Git hook (KILLER FEATURE)
You can set OpenCommit as Git [`prepare-commit-msg`](https://git-scm.com/docs/githooks#_prepare_commit_msg) hook. Hook integrates with you IDE Source Control and allows you edit the message before commit.
To set the hook:
```sh
oc hook set
```
To unset the hook:
```sh
oc hook unset
```
To use the hook:
```sh
git add <files...>
git commit
```
Or follow the process of your IDE Source Control feature, when it calls `git commit` command — OpenCommit will integrate into the flow.
Interactive rebase (`rebase -i`) changes commits' SHA, so the commit history in remote becomes different from your local branch history. This is okay if you work on the branch alone, but may be inconvenient for other collaborators.
## Payments
You pay for your own requests to OpenAI API. OpenCommit uses ChatGPT (3.5-turbo) official model, that is ~15x times cheaper than GPT-4.
You pay for your requests to OpenAI API on your own.
OpenCommit stores your key locally.
OpenCommit by default uses 3.5-turbo-16k model, it should not exceed $0.10 per casual working day.
You may switch to gpt-4, it's better, but more expensive.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

635
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "opencommit",
"version": "2.0.17",
"version": "3.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "opencommit",
"version": "2.0.17",
"version": "3.0.5",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
@@ -16,9 +16,11 @@
"@dqbd/tiktoken": "^1.0.2",
"@octokit/webhooks-schemas": "^6.11.0",
"@octokit/webhooks-types": "^6.11.0",
"ai": "^2.2.14",
"axios": "^1.3.4",
"chalk": "^5.2.0",
"cleye": "^1.3.2",
"crypto": "^1.0.1",
"execa": "^7.0.0",
"ignore": "^5.2.4",
"ini": "^3.0.1",
@@ -26,11 +28,11 @@
"openai": "^3.2.1"
},
"bin": {
"oc": "out/cli.cjs",
"oco": "out/cli.cjs",
"opencommit": "out/cli.cjs"
},
"devDependencies": {
"@commitlint/types": "^17.4.4",
"@types/ini": "^1.3.31",
"@types/inquirer": "^9.0.3",
"@types/node": "^16.18.14",
@@ -150,6 +152,31 @@
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
"integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/parser": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"peer": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@clack/core": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.2.tgz",
@@ -184,6 +211,49 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@commitlint/types": {
"version": "17.4.4",
"resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.4.4.tgz",
"integrity": "sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==",
"dev": true,
"dependencies": {
"chalk": "^4.1.0"
},
"engines": {
"node": ">=v14"
}
},
"node_modules/@commitlint/types/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@commitlint/types/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -298,26 +368,46 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"peer": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"peer": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
@@ -607,6 +697,12 @@
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"node_modules/@types/estree": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz",
"integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
"peer": true
},
"node_modules/@types/ini": {
"version": "1.3.31",
"resolved": "https://registry.npmjs.org/@types/ini/-/ini-1.3.31.tgz",
@@ -838,11 +934,140 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
"integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
"peer": true,
"dependencies": {
"@babel/parser": "^7.21.3",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-core/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"peer": true
},
"node_modules/@vue/compiler-dom": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
"integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
"peer": true,
"dependencies": {
"@vue/compiler-core": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
"integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
"peer": true,
"dependencies": {
"@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4",
"@vue/compiler-dom": "3.3.4",
"@vue/compiler-ssr": "3.3.4",
"@vue/reactivity-transform": "3.3.4",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.0",
"postcss": "^8.1.10",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"peer": true
},
"node_modules/@vue/compiler-ssr": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
"integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/reactivity": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
"integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
"peer": true,
"dependencies": {
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/reactivity-transform": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
"integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
"peer": true,
"dependencies": {
"@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.0"
}
},
"node_modules/@vue/reactivity-transform/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"peer": true
},
"node_modules/@vue/runtime-core": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
"integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
"integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
"peer": true,
"dependencies": {
"@vue/runtime-core": "3.3.4",
"@vue/shared": "3.3.4",
"csstype": "^3.1.1"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
"integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
"peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.3.4",
"@vue/shared": "3.3.4"
},
"peerDependencies": {
"vue": "3.3.4"
}
},
"node_modules/@vue/shared": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==",
"peer": true
},
"node_modules/acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
"dev": true,
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"bin": {
"acorn": "bin/acorn"
},
@@ -868,6 +1093,43 @@
"node": ">=0.4.0"
}
},
"node_modules/ai": {
"version": "2.2.14",
"resolved": "https://registry.npmjs.org/ai/-/ai-2.2.14.tgz",
"integrity": "sha512-4kL2iYPVhH1pl6jJFIJCYcgx5mHzGOmdwiSYWVadmSkNOxKqokgevHyJKiyL9B9DjlreM9cDqkQop56Hdfkb0w==",
"dependencies": {
"eventsource-parser": "1.0.0",
"nanoid": "3.3.6",
"solid-swr-store": "0.10.7",
"sswr": "2.0.0",
"swr": "2.2.0",
"swr-store": "0.10.6",
"swrv": "1.0.4"
},
"engines": {
"node": ">=14.6"
},
"peerDependencies": {
"react": "^18.2.0",
"solid-js": "^1.7.7",
"svelte": "^3.0.0 || ^4.0.0",
"vue": "^3.3.4"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"solid-js": {
"optional": true
},
"svelte": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -941,6 +1203,15 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -965,6 +1236,15 @@
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
"integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==",
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1128,6 +1408,19 @@
"node": ">=0.8"
}
},
"node_modules/code-red": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
"integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",
"@types/estree": "^1.0.1",
"acorn": "^8.10.0",
"estree-walker": "^3.0.3",
"periscopic": "^3.1.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1182,6 +1475,31 @@
"node": ">= 8"
}
},
"node_modules/crypto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
},
"node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
"peer": true,
"dependencies": {
"mdn-data": "2.0.30",
"source-map-js": "^1.0.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
"peer": true
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1229,6 +1547,14 @@
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"engines": {
"node": ">=6"
}
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -1877,6 +2203,15 @@
"node": ">=4.0"
}
},
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"peer": true,
"dependencies": {
"@types/estree": "^1.0.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -1886,6 +2221,14 @@
"node": ">=0.10.0"
}
},
"node_modules/eventsource-parser": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.0.0.tgz",
"integrity": "sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==",
"engines": {
"node": ">=14.18"
}
},
"node_modules/execa": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-7.0.0.tgz",
@@ -2394,6 +2737,15 @@
"node": ">=0.10.0"
}
},
"node_modules/is-reference": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"peer": true,
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
@@ -2431,6 +2783,12 @@
"url": "https://opencollective.com/js-sdsl"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"peer": true
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -2468,6 +2826,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"peer": true
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -2509,6 +2873,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"peer": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -2521,12 +2897,30 @@
"node": ">=10"
}
},
"node_modules/magic-string": {
"version": "0.30.4",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz",
"integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
"peer": true
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -2607,6 +3001,23 @@
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -2851,6 +3262,17 @@
"node": ">=8"
}
},
"node_modules/periscopic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
"integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
"peer": true,
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^3.0.0",
"is-reference": "^3.0.0"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -2868,6 +3290,34 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -2926,6 +3376,18 @@
}
]
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -3086,9 +3548,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
"integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -3100,6 +3562,15 @@
"node": ">=10"
}
},
"node_modules/seroval": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/seroval/-/seroval-0.5.1.tgz",
"integrity": "sha512-ZfhQVB59hmIauJG5Ydynupy8KHyr5imGNtdDhbZG68Ufh1Ynkv9KOYOAABf71oVbQxJ8VkWnMHAjEHE7fWkH5g==",
"peer": true,
"engines": {
"node": ">=10"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3138,6 +3609,48 @@
"node": ">=8"
}
},
"node_modules/solid-js": {
"version": "1.7.12",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.12.tgz",
"integrity": "sha512-QoyoOUKu14iLoGxjxWFIU8+/1kLT4edQ7mZESFPonsEXZ//VJtPKD8Ud1aTKzotj+MNWmSs9YzK6TdY+fO9Eww==",
"peer": true,
"dependencies": {
"csstype": "^3.1.0",
"seroval": "^0.5.0"
}
},
"node_modules/solid-swr-store": {
"version": "0.10.7",
"resolved": "https://registry.npmjs.org/solid-swr-store/-/solid-swr-store-0.10.7.tgz",
"integrity": "sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"solid-js": "^1.2",
"swr-store": "^0.10"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sswr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/sswr/-/sswr-2.0.0.tgz",
"integrity": "sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==",
"dependencies": {
"swrev": "^4.0.0"
},
"peerDependencies": {
"svelte": "^4.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -3234,6 +3747,75 @@
"node": ">=8"
}
},
"node_modules/svelte": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.1.tgz",
"integrity": "sha512-LpLqY2Jr7cRxkrTc796/AaaoMLF/1ax7cto8Ot76wrvKQhrPmZ0JgajiWPmg9mTSDqO16SSLiD17r9MsvAPTmw==",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15",
"@jridgewell/trace-mapping": "^0.3.18",
"acorn": "^8.9.0",
"aria-query": "^5.3.0",
"axobject-query": "^3.2.1",
"code-red": "^1.0.3",
"css-tree": "^2.3.1",
"estree-walker": "^3.0.3",
"is-reference": "^3.0.1",
"locate-character": "^3.0.0",
"magic-string": "^0.30.0",
"periscopic": "^3.1.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/svelte/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"peer": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/swr": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz",
"integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==",
"dependencies": {
"use-sync-external-store": "^1.2.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/swr-store": {
"version": "0.10.6",
"resolved": "https://registry.npmjs.org/swr-store/-/swr-store-0.10.6.tgz",
"integrity": "sha512-xPjB1hARSiRaNNlUQvWSVrG5SirCjk2TmaUyzzvk69SZQan9hCJqw/5rG9iL7xElHU784GxRPISClq4488/XVw==",
"dependencies": {
"dequal": "^2.0.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/swrev": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz",
"integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA=="
},
"node_modules/swrv": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz",
"integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==",
"peerDependencies": {
"vue": ">=3.2.26 < 4"
}
},
"node_modules/terminal-columns": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/terminal-columns/-/terminal-columns-1.4.1.tgz",
@@ -3417,6 +3999,14 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -3436,6 +4026,19 @@
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"node_modules/vue": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
"integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.3.4",
"@vue/compiler-sfc": "3.3.4",
"@vue/runtime-dom": "3.3.4",
"@vue/server-renderer": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "opencommit",
"version": "2.0.17",
"version": "3.0.5",
"description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
"keywords": [
"git",
@@ -12,12 +12,12 @@
"aicommit",
"aicommits",
"gptcommit",
"commit"
"commit",
"ollama"
],
"main": "cli.js",
"bin": {
"opencommit": "./out/cli.cjs",
"oc": "./out/cli.cjs",
"oco": "./out/cli.cjs"
},
"repository": {
@@ -27,7 +27,8 @@
"author": "https://github.com/di-sukharev",
"license": "MIT",
"files": [
"out/**/*"
"out/cli.cjs",
"out/tiktoken_bg.wasm"
],
"release": {
"branches": [
@@ -40,14 +41,16 @@
"scripts": {
"watch": "npm run -S build -- --sourcemap --watch",
"start": "node ./out/cli.cjs",
"ollama:start": "OCO_AI_PROVIDER='ollama' node ./out/cli.cjs",
"dev": "ts-node ./src/cli.ts",
"build": "rimraf out && node esbuild.config.js",
"deploy": "npm run build && npm version patch && npm publish --tag latest",
"build:push": "npm run build && git add . && git commit -m 'build' && git push",
"deploy": "npm version patch && npm run build:push && git push --tags && npm publish --tag latest",
"lint": "eslint src --ext ts && tsc --noEmit",
"format": "prettier --write src"
},
"devDependencies": {
"@commitlint/types": "^17.4.4",
"@types/ini": "^1.3.31",
"@types/inquirer": "^9.0.3",
"@types/node": "^16.18.14",
@@ -68,9 +71,11 @@
"@dqbd/tiktoken": "^1.0.2",
"@octokit/webhooks-schemas": "^6.11.0",
"@octokit/webhooks-types": "^6.11.0",
"ai": "^2.2.14",
"axios": "^1.3.4",
"chalk": "^5.2.0",
"cleye": "^1.3.2",
"crypto": "^1.0.1",
"execa": "^7.0.0",
"ignore": "^5.2.4",
"ini": "^3.0.1",

View File

@@ -1,4 +1,5 @@
export enum COMMANDS {
config = 'config',
hook = 'hook',
config = 'config'
commitlint = 'commitlint'
}

View File

@@ -1,13 +1,14 @@
#!/usr/bin/env node
import { cli } from 'cleye';
import packageJSON from '../package.json' assert { type: 'json' };
import packageJSON from '../package.json';
import { commit } from './commands/commit';
import { commitlintConfigCommand } from './commands/commitlint';
import { configCommand } from './commands/config';
import { hookCommand, isHookCalled } from './commands/githook.js';
import { prepareCommitMessageHook } from './commands/prepare-commit-msg-hook';
import { commit } from './commands/commit';
// import { checkIsLatestVersion } from './utils/checkIsLatestVersion';
import { checkIsLatestVersion } from './utils/checkIsLatestVersion';
const extraArgs = process.argv.slice(2);
@@ -15,13 +16,13 @@ cli(
{
version: packageJSON.version,
name: 'opencommit',
commands: [configCommand, hookCommand],
commands: [configCommand, hookCommand, commitlintConfigCommand],
flags: {},
ignoreArgv: (type) => type === 'unknown-flag' || type === 'argument',
help: { description: packageJSON.description }
},
async () => {
// await checkIsLatestVersion();
await checkIsLatestVersion();
if (await isHookCalled()) {
prepareCommitMessageHook();

9
src/commands/README.md Normal file
View File

@@ -0,0 +1,9 @@
# @commitlint Module for opencommit
1. Load commitlint configuration within tree.
2. Generate a commit with commitlint prompt:
- Will not run if hash is the same.
- Infer a prompt for each commitlint rule.
- Ask OpenAI to generate consistency with embedded commitlint rules.
- Store configuration close to commitlint configuration.
3. Replace conventional-commit prompt with commitlint prompt.

View File

@@ -1,8 +1,17 @@
import chalk from 'chalk';
import { execa } from 'execa';
import {
GenerateCommitMessageErrorEnum,
generateCommitMessageByDiff
} from '../generateCommitMessageFromGitDiff';
confirm,
intro,
isCancel,
multiselect,
outro,
select,
spinner
} from '@clack/prompts';
import { generateCommitMessageByDiff } from '../generateCommitMessageFromGitDiff';
import {
assertGitRepo,
getChangedFiles,
@@ -10,38 +19,51 @@ import {
getStagedFiles,
gitAdd
} from '../utils/git';
import {
spinner,
confirm,
outro,
isCancel,
intro,
multiselect,
select
} from '@clack/prompts';
import chalk from 'chalk';
import { trytm } from '../utils/trytm';
import { getConfig } from './config';
const config = getConfig();
const getGitRemotes = async () => {
const { stdout } = await execa('git', ['remote']);
return stdout.split('\n').filter((remote) => Boolean(remote.trim()));
};
// Check for the presence of message templates
const checkMessageTemplate = (extraArgs: string[]): string | false => {
for (const key in extraArgs) {
if (extraArgs[key].includes(config?.OCO_MESSAGE_TEMPLATE_PLACEHOLDER))
return extraArgs[key];
}
return false;
};
const generateCommitMessageFromGitDiff = async (
diff: string,
extraArgs: string[]
): Promise<void> => {
await assertGitRepo();
const commitSpinner = spinner();
commitSpinner.start('Generating the commit message');
try {
const commitMessage = await generateCommitMessageByDiff(diff);
let commitMessage = await generateCommitMessageByDiff(diff);
const messageTemplate = checkMessageTemplate(extraArgs);
if (
config?.OCO_MESSAGE_TEMPLATE_PLACEHOLDER &&
typeof messageTemplate === 'string'
) {
commitMessage = messageTemplate.replace(
config?.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
commitMessage
);
}
commitSpinner.stop('📝 Commit message generated');
outro(
`Commit message:
`Generated commit message:
${chalk.grey('——————————————————')}
${commitMessage}
${chalk.grey('——————————————————')}`
@@ -79,7 +101,7 @@ ${chalk.grey('——————————————————')}`
if (isPushConfirmedByUser && !isCancel(isPushConfirmedByUser)) {
const pushSpinner = spinner();
pushSpinner.start(`Running \`git push ${remotes[0]}\``);
pushSpinner.start(`Running 'git push ${remotes[0]}'`);
const { stdout } = await execa('git', [
'push',
@@ -107,7 +129,7 @@ ${chalk.grey('——————————————————')}`
if (!isCancel(selectedRemote)) {
const pushSpinner = spinner();
pushSpinner.start(`Running \`git push ${selectedRemote}\``);
pushSpinner.start(`Running 'git push ${selectedRemote}'`);
const { stdout } = await execa('git', ['push', selectedRemote]);
@@ -139,7 +161,7 @@ export async function commit(
if (changedFiles) await gitAdd({ files: changedFiles });
else {
outro('No changes detected, write some code and run `oc` again');
outro('No changes detected, write some code and run `oco` again');
process.exit(1);
}
}

View File

@@ -0,0 +1,46 @@
import chalk from 'chalk';
import { command } from 'cleye';
import { intro, outro } from '@clack/prompts';
import { COMMANDS } from '../CommandsEnum';
import { configureCommitlintIntegration } from '../modules/commitlint/config';
import { getCommitlintLLMConfig } from '../modules/commitlint/utils';
export enum CONFIG_MODES {
get = 'get',
force = 'force'
}
export const commitlintConfigCommand = command(
{
name: COMMANDS.commitlint,
parameters: ['<mode>']
},
async (argv) => {
intro('opencommit — configure @commitlint');
try {
const { mode } = argv._;
if (mode === CONFIG_MODES.get) {
const commitLintConfig = await getCommitlintLLMConfig();
outro(commitLintConfig.toString());
return;
}
if (mode === CONFIG_MODES.force) {
await configureCommitlintIntegration(true);
return;
}
throw new Error(
`Unsupported mode: ${mode}. Valid modes are: "force" and "get"`
);
} catch (error) {
outro(`${chalk.red('✖')} ${error}`);
process.exit(1);
}
}
);

View File

@@ -1,15 +1,16 @@
import { command } from 'cleye';
import { join as pathJoin } from 'path';
import { parse as iniParse, stringify as iniStringify } from 'ini';
import { existsSync, writeFileSync, readFileSync } from 'fs';
import { homedir } from 'os';
import { intro, outro } from '@clack/prompts';
import chalk from 'chalk';
import { command } from 'cleye';
import * as dotenv from 'dotenv';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { parse as iniParse, stringify as iniStringify } from 'ini';
import { homedir } from 'os';
import { join as pathJoin } from 'path';
import { intro, outro } from '@clack/prompts';
import { COMMANDS } from '../CommandsEnum';
import { getI18nLocal } from '../i18n';
import * as dotenv from 'dotenv';
dotenv.config();
export enum CONFIG_KEYS {
@@ -19,9 +20,14 @@ export enum CONFIG_KEYS {
OCO_DESCRIPTION = 'OCO_DESCRIPTION',
OCO_EMOJI = 'OCO_EMOJI',
OCO_MODEL = 'OCO_MODEL',
OCO_LANGUAGE = 'OCO_LANGUAGE'
OCO_LANGUAGE = 'OCO_LANGUAGE',
OCO_MESSAGE_TEMPLATE_PLACEHOLDER = 'OCO_MESSAGE_TEMPLATE_PLACEHOLDER',
OCO_PROMPT_MODULE = 'OCO_PROMPT_MODULE',
OCO_AI_PROVIDER = 'OCO_AI_PROVIDER',
}
export const DEFAULT_MODEL_TOKEN_LIMIT = 4096;
export enum CONFIG_MODES {
get = 'get',
set = 'set'
@@ -42,8 +48,9 @@ const validateConfig = (
};
export const configValidators = {
[CONFIG_KEYS.OCO_OPENAI_API_KEY](value: any) {
validateConfig(CONFIG_KEYS.OCO_OPENAI_API_KEY, value, 'Cannot be empty');
[CONFIG_KEYS.OCO_OPENAI_API_KEY](value: any, config: any = {}) {
//need api key unless running locally with ollama
validateConfig('API_KEY', value || config.OCO_AI_PROVIDER == 'ollama', 'You need to provide an API key');
validateConfig(
CONFIG_KEYS.OCO_OPENAI_API_KEY,
value.startsWith('sk-'),
@@ -51,7 +58,7 @@ export const configValidators = {
);
validateConfig(
CONFIG_KEYS.OCO_OPENAI_API_KEY,
value.length === 51,
config[CONFIG_KEYS.OCO_OPENAI_BASE_PATH] || value.length === 51,
'Must be 51 characters long'
);
@@ -80,7 +87,7 @@ export const configValidators = {
}
validateConfig(
CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS,
typeof value === 'number',
value ? typeof value === 'number' : undefined,
'Must be a number'
);
@@ -117,12 +124,48 @@ export const configValidators = {
[CONFIG_KEYS.OCO_MODEL](value: any) {
validateConfig(
CONFIG_KEYS.OCO_OPENAI_BASE_PATH,
value === 'gpt-3.5-turbo' || value === 'gpt-4',
`${value} is not supported yet, use 'gpt-4' or 'gpt-3.5-turbo' (default)`
CONFIG_KEYS.OCO_MODEL,
[
'gpt-3.5-turbo',
'gpt-4',
'gpt-3.5-turbo-16k',
'gpt-3.5-turbo-0613'
].includes(value),
`${value} is not supported yet, use 'gpt-4', 'gpt-3.5-turbo-16k' (default), 'gpt-3.5-turbo-0613' or 'gpt-3.5-turbo'`
);
return value;
}
},
[CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER](value: any) {
validateConfig(
CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
value.startsWith('$'),
`${value} must start with $, for example: '$msg'`
);
return value;
},
[CONFIG_KEYS.OCO_PROMPT_MODULE](value: any) {
validateConfig(
CONFIG_KEYS.OCO_PROMPT_MODULE,
['conventional-commit', '@commitlint'].includes(value),
`${value} is not supported yet, use '@commitlint' or 'conventional-commit' (default)`
);
return value;
},
[CONFIG_KEYS.OCO_AI_PROVIDER](value: any) {
validateConfig(
CONFIG_KEYS.OCO_AI_PROVIDER,
[
'',
'openai',
'ollama'
].includes(value),
`${value} is not supported yet, use 'ollama' or 'openai' (default)`
);
return value;
},
};
export type ConfigType = {
@@ -134,12 +177,18 @@ const configPath = pathJoin(homedir(), '.opencommit');
export const getConfig = (): ConfigType | null => {
const configFromEnv = {
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
OCO_OPENAI_MAX_TOKENS: Number(process.env.OCO_OPENAI_MAX_TOKENS),
OCO_OPENAI_MAX_TOKENS: process.env.OCO_OPENAI_MAX_TOKENS
? Number(process.env.OCO_OPENAI_MAX_TOKENS)
: undefined,
OCO_OPENAI_BASE_PATH: process.env.OCO_OPENAI_BASE_PATH,
OCO_DESCRIPTION: process.env.OCO_DESCRIPTION === 'true' ? true : false,
OCO_EMOJI: process.env.OCO_EMOJI === 'true' ? true : false,
OCO_MODEL: process.env.OCO_MODEL,
OCO_LANGUAGE: process.env.OCO_LANGUAGE
OCO_MODEL: process.env.OCO_MODEL || 'gpt-3.5-turbo-16k',
OCO_LANGUAGE: process.env.OCO_LANGUAGE || 'en',
OCO_MESSAGE_TEMPLATE_PLACEHOLDER:
process.env.OCO_MESSAGE_TEMPLATE_PLACEHOLDER || '$msg',
OCO_PROMPT_MODULE: process.env.OCO_PROMPT_MODULE || 'conventional-commit',
OCO_AI_PROVIDER: process.env.OCO_AI_PROVIDER || 'openai'
};
const configExists = existsSync(configPath);
@@ -149,17 +198,23 @@ export const getConfig = (): ConfigType | null => {
const config = iniParse(configFile);
for (const configKey of Object.keys(config)) {
if (
!config[configKey] ||
['null', 'undefined'].includes(config[configKey])
) {
config[configKey] = undefined;
continue;
}
try {
const validator = configValidators[configKey as CONFIG_KEYS];
const validValue = validator(
config[configKey] ?? configFromEnv[configKey as CONFIG_KEYS]
config[configKey] ?? configFromEnv[configKey as CONFIG_KEYS],
config
);
config[configKey] = validValue;
} catch (error) {
outro(
`'${configKey}' name is invalid, it should be either 'OCO_${configKey.toUpperCase()}' or it doesn't exist.`
);
outro(`Unknown '${configKey}' config option.`);
outro(
`Manually fix the '.env' file or global '~/.opencommit' config file.`
);

View File

@@ -1,11 +1,13 @@
import chalk from 'chalk';
import { command } from 'cleye';
import { existsSync } from 'fs';
import fs from 'fs/promises';
import path from 'path';
import { command } from 'cleye';
import { assertGitRepo, getCoreHooksPath } from '../utils/git.js';
import { existsSync } from 'fs';
import chalk from 'chalk';
import { intro, outro } from '@clack/prompts';
import { COMMANDS } from '../CommandsEnum.js';
import { assertGitRepo, getCoreHooksPath } from '../utils/git.js';
const HOOK_NAME = 'prepare-commit-msg';
const DEFAULT_SYMLINK_URL = path.join('.git', 'hooks', HOOK_NAME);

View File

@@ -1,9 +1,11 @@
import fs from 'fs/promises';
import chalk from 'chalk';
import fs from 'fs/promises';
import { intro, outro, spinner } from '@clack/prompts';
import { generateCommitMessageByDiff } from '../generateCommitMessageFromGitDiff';
import { getChangedFiles, getDiff, getStagedFiles, gitAdd } from '../utils/git';
import { getConfig } from './config';
import { generateCommitMessageByDiff } from '../generateCommitMessageFromGitDiff';
const [messageFilePath, commitSource] = process.argv.slice(2);
@@ -24,7 +26,7 @@ export const prepareCommitMessageHook = async (
if (changedFiles) await gitAdd({ files: changedFiles });
else {
outro('No changes detected, write some code and run `oc` again');
outro('No changes detected, write some code and run `oco` again');
process.exit(1);
}
}

7
src/engine/Engine.ts Normal file
View File

@@ -0,0 +1,7 @@
import { ChatCompletionRequestMessage } from 'openai';
export interface AiEngine {
generateCommitMessage(
messages: Array<ChatCompletionRequestMessage>
): Promise<string | undefined>;
}

36
src/engine/ollama.ts Normal file
View File

@@ -0,0 +1,36 @@
import axios, { AxiosError } from 'axios';
import { ChatCompletionRequestMessage } from 'openai';
import { AiEngine } from './Engine';
export class OllamaAi implements AiEngine {
async generateCommitMessage(
messages: Array<ChatCompletionRequestMessage>
): Promise<string | undefined> {
const model = 'mistral'; // todo: allow other models
let prompt = messages.map((x) => x.content).join('\n');
//hoftix: local models are not so clever so im changing the prompt a bit...
prompt += 'Summarize above git diff in 10 words or less';
const url = 'http://localhost:11434/api/generate';
const p = {
model,
prompt,
stream: false
};
try {
const response = await axios.post(url, p, {
headers: {
'Content-Type': 'application/json'
}
});
const answer = response.data?.response;
return answer;
} catch (err: any) {
const message = err.response?.data?.error ?? err.message;
throw new Error('local model issues. details: ' + message);
}
}
}
export const ollamaAi = new OllamaAi();

View File

@@ -1,27 +1,39 @@
import { intro, outro } from '@clack/prompts';
import axios from 'axios';
import chalk from 'chalk';
import { execa } from 'execa';
import {
ChatCompletionRequestMessage,
Configuration as OpenAiApiConfiguration,
OpenAIApi
} from 'openai';
import { CONFIG_MODES, getConfig } from './commands/config';
import { intro, outro } from '@clack/prompts';
import {
CONFIG_MODES,
DEFAULT_MODEL_TOKEN_LIMIT,
getConfig
} from '../commands/config';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine } from './Engine';
const config = getConfig();
let maxTokens = config?.OCO_OPENAI_MAX_TOKENS;
let basePath = config?.OCO_OPENAI_BASE_PATH;
let apiKey = config?.OCO_OPENAI_API_KEY;
let apiKey = config?.OCO_OPENAI_API_KEY
const [command, mode] = process.argv.slice(2);
if (!apiKey && command !== 'config' && mode !== CONFIG_MODES.set) {
const isLocalModel = config?.OCO_AI_PROVIDER == 'ollama'
if (!apiKey && command !== 'config' && mode !== CONFIG_MODES.set && !isLocalModel) {
intro('opencommit');
outro(
'OCO_OPENAI_API_KEY is not set, please run `oc config set OCO_OPENAI_API_KEY=<your token>. Make sure you add payment details, so API works.`'
'OCO_OPENAI_API_KEY is not set, please run `oco config set OCO_OPENAI_API_KEY=<your token> . If you are using GPT, make sure you add payment details, so API works.`'
);
outro(
'For help look into README https://github.com/di-sukharev/opencommit#setup'
@@ -32,7 +44,7 @@ if (!apiKey && command !== 'config' && mode !== CONFIG_MODES.set) {
const MODEL = config?.OCO_MODEL || 'gpt-3.5-turbo';
class OpenAi {
class OpenAi implements AiEngine {
private openAiApiConfiguration = new OpenAiApiConfiguration({
apiKey: apiKey
});
@@ -56,6 +68,14 @@ class OpenAi {
max_tokens: maxTokens || 500
};
try {
const REQUEST_TOKENS = messages
.map((msg) => tokenCount(msg.content) + 4)
.reduce((a, b) => a + b, 0);
if (REQUEST_TOKENS > DEFAULT_MODEL_TOKEN_LIMIT - maxTokens) {
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
}
const { data } = await this.openAI.createChatCompletion(params);
const message = data.choices[0].message;
@@ -84,18 +104,6 @@ class OpenAi {
};
}
export const getOpenCommitLatestVersion = async (): Promise<
string | undefined
> => {
try {
const { data } = await axios.get(
'https://unpkg.com/opencommit/package.json'
);
return data.version;
} catch (_) {
outro('Error while getting the latest version of opencommit');
return undefined;
}
};
export const api = new OpenAi();

View File

@@ -2,62 +2,20 @@ import {
ChatCompletionRequestMessage,
ChatCompletionRequestMessageRoleEnum
} from 'openai';
import { api } from './api';
import { getConfig } from './commands/config';
import { DEFAULT_MODEL_TOKEN_LIMIT, getConfig } from './commands/config';
import { getMainCommitPrompt } from './prompts';
import { mergeDiffs } from './utils/mergeDiffs';
import { i18n, I18nLocals } from './i18n';
import { tokenCount } from './utils/tokenCount';
import { getEngine } from './utils/engine';
const config = getConfig();
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
const INIT_MESSAGES_PROMPT: Array<ChatCompletionRequestMessage> = [
{
role: ChatCompletionRequestMessageRoleEnum.System,
// prettier-ignore
content: `You are to act as the author of a commit message in git. Your mission is to create clean and comprehensive commit messages in the conventional commit convention and explain WHAT were the changes and WHY the changes were done. I'll send you an output of 'git diff --staged' command, and you convert it into a commit message.
${config?.OCO_EMOJI ? 'Use GitMoji convention to preface the commit.': 'Do not preface the commit with anything.'}
${config?.OCO_DESCRIPTION ? 'Add a short description of WHY the changes are done after the commit message. Don\'t start it with "This commit", just describe the changes.': "Don't add any descriptions to the commit, only commit message."}
Use the present tense. Lines must not be longer than 74 characters. Use ${translation.localLanguage} to answer.`
},
{
role: ChatCompletionRequestMessageRoleEnum.User,
content: `diff --git a/src/server.ts b/src/server.ts
index ad4db42..f3b18a9 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -10,7 +10,7 @@
import {
initWinstonLogger();
const app = express();
-const port = 7799;
+const PORT = 7799;
app.use(express.json());
@@ -34,6 +34,6 @@
app.use((_, res, next) => {
// ROUTES
app.use(PROTECTED_ROUTER_URL, protectedRouter);
-app.listen(port, () => {
- console.log(\`Server listening on port \${port}\`);
+app.listen(process.env.PORT || PORT, () => {
+ console.log(\`Server listening on port \${PORT}\`);
});`
},
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: `${config?.OCO_EMOJI ? '🐛 ' : ''}${translation.commitFix}
${config?.OCO_EMOJI ? '✨ ' : ''}${translation.commitFeat}
${config?.OCO_DESCRIPTION ? translation.commitDescription : ''}`
}
];
const generateCommitMessageChatCompletionPrompt = (
const generateCommitMessageChatCompletionPrompt = async (
diff: string
): Array<ChatCompletionRequestMessage> => {
): Promise<Array<ChatCompletionRequestMessage>> => {
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
const chatContextAsCompletionRequest = [...INIT_MESSAGES_PROMPT];
chatContextAsCompletionRequest.push({
@@ -74,39 +32,48 @@ export enum GenerateCommitMessageErrorEnum {
emptyMessage = 'EMPTY_MESSAGE'
}
interface GenerateCommitMessageError {
error: GenerateCommitMessageErrorEnum;
}
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
(msg) => tokenCount(msg.content) + 4
).reduce((a, b) => a + b, 0);
const MAX_REQ_TOKENS = 3000 - INIT_MESSAGES_PROMPT_LENGTH;
const ADJUSTMENT_FACTOR = 20;
export const generateCommitMessageByDiff = async (
diff: string
): Promise<string> => {
try {
if (tokenCount(diff) >= MAX_REQ_TOKENS) {
const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
(msg) => tokenCount(msg.content) + 4
).reduce((a, b) => a + b, 0);
const MAX_REQUEST_TOKENS =
DEFAULT_MODEL_TOKEN_LIMIT -
ADJUSTMENT_FACTOR -
INIT_MESSAGES_PROMPT_LENGTH -
config?.OCO_OPENAI_MAX_TOKENS;
if (tokenCount(diff) >= MAX_REQUEST_TOKENS) {
const commitMessagePromises = await getCommitMsgsPromisesFromFileDiffs(
diff,
MAX_REQ_TOKENS
MAX_REQUEST_TOKENS
);
const commitMessages = await Promise.all(commitMessagePromises);
const commitMessages = [];
for (const promise of commitMessagePromises) {
commitMessages.push(await promise);
await delay(2000);
}
return commitMessages.join('\n\n');
} else {
const messages = generateCommitMessageChatCompletionPrompt(diff);
const commitMessage = await api.generateCommitMessage(messages);
if (!commitMessage)
throw new Error(GenerateCommitMessageErrorEnum.emptyMessage);
return commitMessage;
}
const messages = await generateCommitMessageChatCompletionPrompt(diff);
const engine = getEngine()
const commitMessage = await engine.generateCommitMessage(messages);
if (!commitMessage)
throw new Error(GenerateCommitMessageErrorEnum.emptyMessage);
return commitMessage;
} catch (error) {
throw error;
}
@@ -126,25 +93,68 @@ function getMessagesPromisesByChangesInFile(
maxChangeLength
);
const lineDiffsWithHeader = mergedChanges.map(
(change) => fileHeader + change
const lineDiffsWithHeader = [];
for (const change of mergedChanges) {
const totalChange = fileHeader + change;
if (tokenCount(totalChange) > maxChangeLength) {
// If the totalChange is too large, split it into smaller pieces
const splitChanges = splitDiff(totalChange, maxChangeLength);
lineDiffsWithHeader.push(...splitChanges);
} else {
lineDiffsWithHeader.push(totalChange);
}
}
const engine = getEngine()
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map(
async (lineDiff) => {
const messages = await generateCommitMessageChatCompletionPrompt(
separator + lineDiff
);
return engine.generateCommitMessage(messages);
}
);
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map((lineDiff) => {
const messages = generateCommitMessageChatCompletionPrompt(
separator + lineDiff
);
return api.generateCommitMessage(messages);
});
return commitMsgsFromFileLineDiffs;
}
export function getCommitMsgsPromisesFromFileDiffs(
function splitDiff(diff: string, maxChangeLength: number) {
const lines = diff.split('\n');
const splitDiffs = [];
let currentDiff = '';
for (let line of lines) {
// If a single line exceeds maxChangeLength, split it into multiple lines
while (tokenCount(line) > maxChangeLength) {
const subLine = line.substring(0, maxChangeLength);
line = line.substring(maxChangeLength);
splitDiffs.push(subLine);
}
// Check the tokenCount of the currentDiff and the line separately
if (tokenCount(currentDiff) + tokenCount('\n' + line) > maxChangeLength) {
// If adding the next line would exceed the maxChangeLength, start a new diff
splitDiffs.push(currentDiff);
currentDiff = line;
} else {
// Otherwise, add the line to the current diff
currentDiff += '\n' + line;
}
}
// Add the last diff
if (currentDiff) {
splitDiffs.push(currentDiff);
}
return splitDiffs;
}
export const getCommitMsgsPromisesFromFileDiffs = async (
diff: string,
maxDiffLength: number
) {
) => {
const separator = 'diff --git ';
const diffByFiles = diff.split(separator).slice(1);
@@ -165,12 +175,18 @@ export function getCommitMsgsPromisesFromFileDiffs(
commitMessagePromises.push(...messagesPromises);
} else {
const messages = generateCommitMessageChatCompletionPrompt(
const messages = await generateCommitMessageChatCompletionPrompt(
separator + fileDiff
);
commitMessagePromises.push(api.generateCommitMessage(messages));
const engine = getEngine()
commitMessagePromises.push(engine.generateCommitMessage(messages));
}
}
return commitMessagePromises;
};
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -1,12 +1,14 @@
import { unlinkSync, writeFileSync } from 'fs';
import core from '@actions/core';
import github from '@actions/github';
import exec from '@actions/exec';
import github from '@actions/github';
import { intro, outro } from '@clack/prompts';
import { PushEvent } from '@octokit/webhooks-types';
import { generateCommitMessageByDiff } from './generateCommitMessageFromGitDiff';
import { sleep } from './utils/sleep';
import { randomIntFromInterval } from './utils/randomIntFromInterval';
import { unlinkSync, writeFileSync } from 'fs';
import { sleep } from './utils/sleep';
// This should be a token with access to your repository scoped in as a secret.
// The YML workflow will need to set GITHUB_TOKEN with the GitHub Secret Token
@@ -133,6 +135,16 @@ async function improveCommitMessages(
improvedMessagesWithSHAs
);
// Check if there are actually any changes in the commit messages
const messagesChanged = improvedMessagesWithSHAs.some(
({ sha, msg }, index) => msg !== commitsToImprove[index].message
);
if (!messagesChanged) {
console.log('No changes in commit messages detected, skipping rebase');
return;
}
const createCommitMessageFile = (message: string, index: number) =>
writeFileSync(`./commit-${index}.txt`, message);
improvedMessagesWithSHAs.forEach(({ msg }, i) =>

View File

@@ -1,22 +1,22 @@
import en from '../i18n/en.json' assert { type: 'json' };
import cs from '../i18n/cs.json' assert { type: 'json' };
import de from '../i18n/de.json' assert { type: 'json' };
import fr from '../i18n/fr.json' assert { type: 'json' };
import it from '../i18n/it.json' assert { type: 'json' };
import ko from '../i18n/ko.json' assert { type: 'json' };
import zh_CN from '../i18n/zh_CN.json' assert { type: 'json' };
import zh_TW from '../i18n/zh_TW.json' assert { type: 'json' };
import ja from '../i18n/ja.json' assert { type: 'json' };
import pt_br from '../i18n/pt_br.json' assert { type: 'json' };
import vi_VN from '../i18n/vi_VN.json' assert { type: 'json' };
import es_ES from '../i18n/es_ES.json' assert { type: 'json' };
import sv from '../i18n/sv.json' assert { type: 'json' };
import nl from '../i18n/nl.json' assert { type: 'json' };
import ru from '../i18n/ru.json' assert { type: 'json' };
import id_ID from '../i18n/id_ID.json' assert { type: 'json' };
import pl from '../i18n/pl.json' assert { type: 'json' };
import tr from '../i18n/tr.json' assert { type: 'json' };
import th from '../i18n/th.json' assert { type: 'json' };
import cs from '../i18n/cs.json';
import de from '../i18n/de.json';
import en from '../i18n/en.json';
import es_ES from '../i18n/es_ES.json';
import fr from '../i18n/fr.json';
import id_ID from '../i18n/id_ID.json';
import it from '../i18n/it.json';
import ja from '../i18n/ja.json';
import ko from '../i18n/ko.json';
import nl from '../i18n/nl.json';
import pl from '../i18n/pl.json';
import pt_br from '../i18n/pt_br.json';
import ru from '../i18n/ru.json';
import sv from '../i18n/sv.json';
import th from '../i18n/th.json';
import tr from '../i18n/tr.json';
import vi_VN from '../i18n/vi_VN.json';
import zh_CN from '../i18n/zh_CN.json';
import zh_TW from '../i18n/zh_TW.json';
export enum I18nLocals {
'en' = 'en',
@@ -36,7 +36,7 @@ export enum I18nLocals {
'id_ID' = 'id_ID',
'pl' = 'pl',
'tr' = 'tr',
'th' = 'th',
'th' = 'th'
}
export const i18n = {

View File

@@ -0,0 +1,80 @@
import { spinner } from '@clack/prompts';
import { getConfig } from '../../commands/config';
import { i18n, I18nLocals } from '../../i18n';
import { COMMITLINT_LLM_CONFIG_PATH } from './constants';
import { computeHash } from './crypto';
import { commitlintPrompts, inferPromptsFromCommitlintConfig } from './prompts';
import { getCommitLintPWDConfig } from './pwd-commitlint';
import { CommitlintLLMConfig } from './types';
import * as utils from './utils';
import { getEngine } from '../../utils/engine';
const config = getConfig();
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
export const configureCommitlintIntegration = async (force = false) => {
const spin = spinner();
spin.start('Loading @commitlint configuration');
const fileExists = await utils.commitlintLLMConfigExists();
let commitLintConfig = await getCommitLintPWDConfig();
// debug complete @commitlint configuration
// await fs.writeFile(
// `${OPENCOMMIT_COMMITLINT_CONFIG}-commitlint-debug`,
// JSON.stringify(commitLintConfig, null, 2)
// );
const hash = await computeHash(JSON.stringify(commitLintConfig));
spin.stop(`Read @commitlint configuration (hash: ${hash})`);
if (fileExists) {
// Check if we need to update the prompts.
const { hash: existingHash } = await utils.getCommitlintLLMConfig();
if (hash === existingHash && !force) {
spin.stop(
'Hashes are the same, no need to update the config. Run "force" command to bypass.'
);
return;
}
}
spin.start('Generating consistency with given @commitlint rules');
const prompts = inferPromptsFromCommitlintConfig(commitLintConfig);
const consistencyPrompts =
commitlintPrompts.GEN_COMMITLINT_CONSISTENCY_PROMPT(prompts);
// debug prompt which will generate a consistency
// await fs.writeFile(
// `${COMMITLINT_LLM_CONFIG}-debug`,
// consistencyPrompts.map((p) => p.content)
// );
const engine = getEngine()
let consistency =
(await engine.generateCommitMessage(consistencyPrompts)) || '{}';
// Cleanup the consistency answer. Sometimes 'gpt-3.5-turbo' sends rule's back.
prompts.forEach((prompt) => (consistency = consistency.replace(prompt, '')));
// ... remaining might be extra set of "\n"
consistency = utils.removeDoubleNewlines(consistency);
const commitlintLLMConfig: CommitlintLLMConfig = {
hash,
prompts,
consistency: {
[translation.localLanguage]: {
...JSON.parse(consistency as string)
}
}
};
await utils.writeCommitlintLLMConfig(commitlintLLMConfig);
spin.stop(`Done - please review contents of ${COMMITLINT_LLM_CONFIG_PATH}`);
};

View File

@@ -0,0 +1 @@
export const COMMITLINT_LLM_CONFIG_PATH = `${process.env.PWD}/.opencommit-commitlint`;

View File

@@ -0,0 +1,15 @@
import crypto from 'crypto';
export const computeHash = async (
content: string,
algorithm: string = 'sha256'
): Promise<string> => {
try {
const hash = crypto.createHash(algorithm);
hash.update(content);
return hash.digest('hex');
} catch (error) {
console.error('Error while computing hash:', error);
throw error;
}
};

View File

@@ -0,0 +1,283 @@
import chalk from 'chalk';
import {
ChatCompletionRequestMessage,
ChatCompletionRequestMessageRoleEnum
} from 'openai';
import { outro } from '@clack/prompts';
import {
PromptConfig,
QualifiedConfig,
RuleConfigSeverity,
RuleConfigTuple
} from '@commitlint/types';
import { getConfig } from '../../commands/config';
import { i18n, I18nLocals } from '../../i18n';
import { IDENTITY, INIT_DIFF_PROMPT } from '../../prompts';
const config = getConfig();
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
type DeepPartial<T> = {
[P in keyof T]?: {
[K in keyof T[P]]?: T[P][K];
};
};
type PromptFunction = (
applicable: string,
value: any,
prompt: DeepPartial<PromptConfig>
) => string;
type PromptResolverFunction = (
key: string,
applicable: string,
value: any,
prompt?: DeepPartial<PromptConfig>
) => string;
/**
* Extracts more contexte for each type-enum.
* IDEA: replicate the concept for scopes and refactor to a generic feature.
*/
const getTypeRuleExtraDescription = (
type: string,
prompt?: DeepPartial<PromptConfig>
) => prompt?.questions?.type?.enum?.[type]?.description;
/*
IDEA: Compress llm readable prompt for each section of commit message: one line for header, one line for scope, etc.
- The type must be in lowercase and should be one of the following values: featuring, fixing, documenting, styling, refactoring, testing, chores, perf, build, ci, revert.
- The scope should not be empty and provide context for the change (e.g., module or file changed).
- The subject should not be empty, should not end with a period, and should provide a concise description of the change. It should not be in sentence-case, start-case, pascal-case, or upper-case.
*/
const llmReadableRules: {
[ruleName: string]: PromptResolverFunction;
} = {
blankline: (key, applicable) =>
`There should ${applicable} be a blank line at the beginning of the ${key}.`,
caseRule: (key, applicable, value: string | Array<string>) =>
`The ${key} should ${applicable} be in ${
Array.isArray(value)
? `one of the following case:
- ${value.join('\n - ')}.`
: `${value} case.`
}`,
emptyRule: (key, applicable) => `The ${key} should ${applicable} be empty.`,
enumRule: (key, applicable, value: string | Array<string>) =>
`The ${key} should ${applicable} be one of the following values:
- ${Array.isArray(value) ? value.join('\n - ') : value}.`,
enumTypeRule: (key, applicable, value: string | Array<string>, prompt) =>
`The ${key} should ${applicable} be one of the following values:
- ${
Array.isArray(value)
? value
.map((v) => {
const description = getTypeRuleExtraDescription(v, prompt);
if (description) {
return `${v} (${description})`;
} else return v;
})
.join('\n - ')
: value
}.`,
fullStopRule: (key, applicable, value: string) =>
`The ${key} should ${applicable} end with '${value}'.`,
maxLengthRule: (key, applicable, value: string) =>
`The ${key} should ${applicable} have ${value} characters or less.`,
minLengthRule: (key, applicable, value: string) =>
`The ${key} should ${applicable} have ${value} characters or more.`
};
/**
* TODO: Validate rules to every rule in the @commitlint configuration.
* IDEA: Plugins can extend the list of rule. Provide user with a way to infer or extend when "No prompt handler for rule".
*/
const rulesPrompts: {
[ruleName: string]: PromptFunction;
} = {
'body-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('body', applicable, value),
'body-empty': (applicable: string) =>
llmReadableRules.emptyRule('body', applicable, undefined),
'body-full-stop': (applicable: string, value: string) =>
llmReadableRules.fullStopRule('body', applicable, value),
'body-leading-blank': (applicable: string) =>
llmReadableRules.blankline('body', applicable, undefined),
'body-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('body', applicable, value),
'body-max-line-length': (applicable: string, value: string) =>
`Each line of the body should ${applicable} have ${value} characters or less.`,
'body-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('body', applicable, value),
'footer-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('footer', applicable, value),
'footer-empty': (applicable: string) =>
llmReadableRules.emptyRule('footer', applicable, undefined),
'footer-leading-blank': (applicable: string) =>
llmReadableRules.blankline('footer', applicable, undefined),
'footer-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('footer', applicable, value),
'footer-max-line-length': (applicable: string, value: string) =>
`Each line of the footer should ${applicable} have ${value} characters or less.`,
'footer-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('footer', applicable, value),
'header-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('header', applicable, value),
'header-full-stop': (applicable: string, value: string) =>
llmReadableRules.fullStopRule('header', applicable, value),
'header-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('header', applicable, value),
'header-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('header', applicable, value),
'references-empty': (applicable: string) =>
llmReadableRules.emptyRule('references section', applicable, undefined),
'scope-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('scope', applicable, value),
'scope-empty': (applicable: string) =>
llmReadableRules.emptyRule('scope', applicable, undefined),
'scope-enum': (applicable: string, value: string | Array<string>) =>
llmReadableRules.enumRule('type', applicable, value),
'scope-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('scope', applicable, value),
'scope-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('scope', applicable, value),
'signed-off-by': (applicable: string, value: string) =>
`The commit message should ${applicable} have a "Signed-off-by" line with the value "${value}".`,
'subject-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('subject', applicable, value),
'subject-empty': (applicable: string) =>
llmReadableRules.emptyRule('subject', applicable, undefined),
'subject-full-stop': (applicable: string, value: string) =>
llmReadableRules.fullStopRule('subject', applicable, value),
'subject-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('subject', applicable, value),
'subject-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('subject', applicable, value),
'type-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('type', applicable, value),
'type-empty': (applicable: string) =>
llmReadableRules.emptyRule('type', applicable, undefined),
'type-enum': (applicable: string, value: string | Array<string>, prompt) =>
llmReadableRules.enumTypeRule('type', applicable, value, prompt),
'type-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('type', applicable, value),
'type-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('type', applicable, value)
};
const getPrompt = (
ruleName: string,
ruleConfig: RuleConfigTuple<unknown>,
prompt: DeepPartial<PromptConfig>
) => {
const [severity, applicable, value] = ruleConfig;
// Should we exclude "Disabled" properties?
// Is this used to disable a subjacent rule when extending presets?
if (severity === RuleConfigSeverity.Disabled) return null;
const promptFn = rulesPrompts[ruleName];
if (promptFn) {
return promptFn(applicable, value, prompt);
}
// Plugins may add their custom rules.
// We might want to call OpenAI to build this rule's llm-readable prompt.
outro(`${chalk.red('✖')} No prompt handler for rule "${ruleName}".`);
return `Please manualy set the prompt for rule "${ruleName}".`;
};
export const inferPromptsFromCommitlintConfig = (
config: QualifiedConfig
): string[] => {
const { rules, prompt } = config;
if (!rules) return [];
return Object.keys(rules)
.map((ruleName) =>
getPrompt(ruleName, rules[ruleName] as RuleConfigTuple<unknown>, prompt)
)
.filter((prompt) => prompt !== null) as string[];
};
/**
* Breaking down commit message structure for conventional commit, and mapping bits with
* ubiquitous language from @commitlint.
* While gpt-4 does this on it self, gpt-3.5 can't map this on his own atm.
*/
const STRUCTURE_OF_COMMIT = `
- Header of commit is composed of type, scope, subject: <type-of-commit>(<scope-of-commit>): <subject-of-commit>
- Description of commit is composed of body and footer (optional): <body-of-commit>\n<footer(s)-of-commit>`;
// Prompt to generate LLM-readable rules based on @commitlint rules.
const GEN_COMMITLINT_CONSISTENCY_PROMPT = (
prompts: string[]
): ChatCompletionRequestMessage[] => [
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
// prettier-ignore
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages for two different changes in a single codebase and output them in the provided JSON format: one for a bug fix and another for a new feature.
Here are the specific requirements and conventions that should be strictly followed:
Commit Message Conventions:
- The commit message consists of three parts: Header, Body, and Footer.
- Header:
- Format: \`<type>(<scope>): <subject>\`
- ${prompts.join('\n- ')}
JSON Output Format:
- The JSON output should contain the commit messages for a bug fix and a new feature in the following format:
\`\`\`json
{
"localLanguage": "${translation.localLanguage}",
"commitFix": "<Header of commit for bug fix>",
"commitFeat": "<Header of commit for feature>",
"commitDescription": "<Description of commit for both the bug fix and the feature>"
}
\`\`\`
- The "commitDescription" should not include the commit messages header, only the description.
- Description should not be more than 74 characters.
Additional Details:
- Changing the variable 'port' to uppercase 'PORT' is considered a bug fix.
- Allowing the server to listen on a port specified through the environment variable is considered a new feature.
Example Git Diff is to follow:`
},
INIT_DIFF_PROMPT
];
/**
* Prompt to have LLM generate a message using @commitlint rules.
*
* @param language
* @param prompts
* @returns
*/
const INIT_MAIN_PROMPT = (
language: string,
prompts: string[]
): ChatCompletionRequestMessage => ({
role: ChatCompletionRequestMessageRoleEnum.System,
// prettier-ignore
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages in the given @commitlint convention and explain WHAT were the changes and WHY the changes were done. I'll send you an output of 'git diff --staged' command, and you convert it into a commit message.
${config?.OCO_EMOJI ? 'Use GitMoji convention to preface the commit.' : 'Do not preface the commit with anything.'}
${config?.OCO_DESCRIPTION ? 'Add a short description of WHY the changes are done after the commit message. Don\'t start it with "This commit", just describe the changes.' : "Don't add any descriptions to the commit, only commit message."}
Use the present tense. Use ${language} to answer.
You will strictly follow the following conventions to generate the content of the commit message:
- ${prompts.join('\n- ')}
The conventions refers to the following structure of commit message:
${STRUCTURE_OF_COMMIT}
`
});
export const commitlintPrompts = {
INIT_MAIN_PROMPT,
GEN_COMMITLINT_CONSISTENCY_PROMPT
};

View File

@@ -0,0 +1,25 @@
import path from 'path';
const nodeModulesPath = path.join(
process.env.PWD || process.cwd(),
'node_modules',
'@commitlint',
'load'
);
/**
* This code is loading the configuration for the `@commitlint` package from the current working
* directory (`process.env.PWD`) by requiring the `load` module from the `@commitlint` package.
*
* @returns
*/
export const getCommitLintPWDConfig = async () => {
const load = require(nodeModulesPath).default;
if (load && typeof load === 'function') {
return await load();
}
// @commitlint/load is not a function
return null;
};

View File

@@ -0,0 +1,11 @@
import { i18n } from '../../i18n';
export type ConsistencyPrompt = (typeof i18n)[keyof typeof i18n];
export type CommitlintLLMConfig = {
hash: string;
prompts: string[];
consistency: {
[key: string]: ConsistencyPrompt;
};
};

View File

@@ -0,0 +1,47 @@
import fs from 'fs/promises';
import { COMMITLINT_LLM_CONFIG_PATH } from './constants';
import { CommitlintLLMConfig } from './types';
/**
* Removes the "\n" only if occurring twice
*/
export const removeDoubleNewlines = (input: string): string => {
const pattern = /\\n\\n/g;
if (pattern.test(input)) {
const newInput = input.replace(pattern, '');
return removeDoubleNewlines(newInput);
}
return input;
};
export const commitlintLLMConfigExists = async (): Promise<boolean> => {
let exists;
try {
await fs.access(COMMITLINT_LLM_CONFIG_PATH);
exists = true;
} catch (e) {
exists = false;
}
return exists;
};
export const writeCommitlintLLMConfig = async (
commitlintLLMConfig: CommitlintLLMConfig
): Promise<void> => {
await fs.writeFile(
COMMITLINT_LLM_CONFIG_PATH,
JSON.stringify(commitlintLLMConfig, null, 2)
);
};
export const getCommitlintLLMConfig =
async (): Promise<CommitlintLLMConfig> => {
const content = await fs.readFile(COMMITLINT_LLM_CONFIG_PATH);
const commitLintLLMConfig = JSON.parse(
content.toString()
) as CommitlintLLMConfig;
return commitLintLLMConfig;
};

110
src/prompts.ts Normal file
View File

@@ -0,0 +1,110 @@
import {
ChatCompletionRequestMessage,
ChatCompletionRequestMessageRoleEnum
} from 'openai';
import { note } from '@clack/prompts';
import { getConfig } from './commands/config';
import { i18n, I18nLocals } from './i18n';
import { configureCommitlintIntegration } from './modules/commitlint/config';
import { commitlintPrompts } from './modules/commitlint/prompts';
import { ConsistencyPrompt } from './modules/commitlint/types';
import * as utils from './modules/commitlint/utils';
const config = getConfig();
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
export const IDENTITY =
'You are to act as the author of a commit message in git.';
const INIT_MAIN_PROMPT = (language: string): ChatCompletionRequestMessage => ({
role: ChatCompletionRequestMessageRoleEnum.System,
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages as per the conventional commit convention and explain WHAT were the changes and mainly WHY the changes were done. I'll send you an output of 'git diff --staged' command, and you are to convert it into a commit message.
${
config?.OCO_EMOJI
? 'Use GitMoji convention to preface the commit.'
: 'Do not preface the commit with anything.'
}
${
config?.OCO_DESCRIPTION
? 'Add a short description of WHY the changes are done after the commit message. Don\'t start it with "This commit", just describe the changes.'
: "Don't add any descriptions to the commit, only commit message."
}
Use the present tense. Lines must not be longer than 74 characters. Use ${language} for the commit message.`
});
export const INIT_DIFF_PROMPT: ChatCompletionRequestMessage = {
role: ChatCompletionRequestMessageRoleEnum.User,
content: `diff --git a/src/server.ts b/src/server.ts
index ad4db42..f3b18a9 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -10,7 +10,7 @@
import {
initWinstonLogger();
const app = express();
-const port = 7799;
+const PORT = 7799;
app.use(express.json());
@@ -34,6 +34,6 @@
app.use((_, res, next) => {
// ROUTES
app.use(PROTECTED_ROUTER_URL, protectedRouter);
-app.listen(port, () => {
- console.log(\`Server listening on port \${port}\`);
+app.listen(process.env.PORT || PORT, () => {
+ console.log(\`Server listening on port \${PORT}\`);
});`
};
const INIT_CONSISTENCY_PROMPT = (
translation: ConsistencyPrompt
): ChatCompletionRequestMessage => ({
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: `${config?.OCO_EMOJI ? '🐛 ' : ''}${translation.commitFix}
${config?.OCO_EMOJI ? '✨ ' : ''}${translation.commitFeat}
${config?.OCO_DESCRIPTION ? translation.commitDescription : ''}`
});
export const getMainCommitPrompt = async (): Promise<
ChatCompletionRequestMessage[]
> => {
switch (config?.OCO_PROMPT_MODULE) {
case '@commitlint':
if (!(await utils.commitlintLLMConfigExists())) {
note(
`OCO_PROMPT_MODULE is @commitlint but you haven't generated consistency for this project yet.`
);
await configureCommitlintIntegration();
}
// Replace example prompt with a prompt that's generated by OpenAI for the commitlint config.
const commitLintConfig = await utils.getCommitlintLLMConfig();
return [
commitlintPrompts.INIT_MAIN_PROMPT(
translation.localLanguage,
commitLintConfig.prompts
),
INIT_DIFF_PROMPT,
INIT_CONSISTENCY_PROMPT(
commitLintConfig.consistency[
translation.localLanguage
] as ConsistencyPrompt
)
];
default:
// conventional-commit
return [
INIT_MAIN_PROMPT(translation.localLanguage),
INIT_DIFF_PROMPT,
INIT_CONSISTENCY_PROMPT(translation)
];
}
};

View File

@@ -1,7 +1,10 @@
import { getOpenCommitLatestVersion } from '../api';
import currentPackage from '../../package.json' assert { type: 'json' };
import chalk from 'chalk';
import { outro } from '@clack/prompts';
import currentPackage from '../../package.json';
import { getOpenCommitLatestVersion } from '../version';
export const checkIsLatestVersion = async () => {
const latestVersion = await getOpenCommitLatestVersion();
@@ -9,7 +12,7 @@ export const checkIsLatestVersion = async () => {
const currentVersion = currentPackage.version;
if (currentVersion !== latestVersion) {
console.warn(
outro(
chalk.yellow(
`
You are not using the latest stable version of OpenCommit with new features and bug fixes.

13
src/utils/engine.ts Normal file
View File

@@ -0,0 +1,13 @@
import { AiEngine } from '../engine/Engine';
import { api } from '../engine/openAi';
import { getConfig } from '../commands/config';
import { ollamaAi } from '../engine/ollama';
export function getEngine(): AiEngine {
const config = getConfig();
if (config?.OCO_AI_PROVIDER == 'ollama') {
return ollamaAi;
}
//open ai gpt by default
return api;
}

View File

@@ -1,8 +1,9 @@
import { execa } from 'execa';
import { outro, spinner } from '@clack/prompts';
import { readFileSync } from 'fs';
import ignore, { Ignore } from 'ignore';
import { outro, spinner } from '@clack/prompts';
export const assertGitRepo = async () => {
try {
await execa('git', ['rev-parse']);
@@ -25,13 +26,11 @@ export const getOpenCommitIgnore = (): Ignore => {
return ig;
};
export const getCoreHooksPath = async(): Promise<string> => {
const { stdout } = await execa('git', [
'config',
'core.hooksPath']);
export const getCoreHooksPath = async (): Promise<string> => {
const { stdout } = await execa('git', ['config', 'core.hooksPath']);
return stdout;
}
};
export const getStagedFiles = async (): Promise<string[]> => {
const { stdout: gitDir } = await execa('git', [
@@ -83,12 +82,20 @@ export const gitAdd = async ({ files }: { files: string[] }) => {
export const getDiff = async ({ files }: { files: string[] }) => {
const lockFiles = files.filter(
(file) => file.includes('.lock') || file.includes('-lock.')
(file) =>
file.includes('.lock') ||
file.includes('-lock.') ||
file.includes('.svg') ||
file.includes('.png') ||
file.includes('.jpg') ||
file.includes('.jpeg') ||
file.includes('.webp') ||
file.includes('.gif')
);
if (lockFiles.length) {
outro(
`Some files are '.lock' files which are excluded by default from 'git diff'. No commit messages are generated for this files:\n${lockFiles.join(
`Some files are excluded by default from 'git diff'. No commit messages are generated for this files:\n${lockFiles.join(
'\n'
)}`
);

View File

@@ -1,4 +1,5 @@
import { tokenCount } from './tokenCount';
export function mergeDiffs(arr: string[], maxStringLength: number): string[] {
const mergedArr: string[] = [];
let currentItem: string = arr[0];

View File

@@ -1,5 +1,5 @@
import cl100k_base from '@dqbd/tiktoken/encoders/cl100k_base.json';
import { Tiktoken } from '@dqbd/tiktoken/lite';
import cl100k_base from '@dqbd/tiktoken/encoders/cl100k_base.json' assert { type: 'json' };
export function tokenCount(content: string): number {
const encoding = new Tiktoken(

14
src/version.ts Normal file
View File

@@ -0,0 +1,14 @@
import { outro } from "@clack/prompts";
import { execa } from "execa";
export const getOpenCommitLatestVersion = async (): Promise<
string | undefined
> => {
try {
const { stdout } = await execa('npm', ['view', 'opencommit', 'version']);
return stdout;
} catch (_) {
outro('Error while getting the latest version of opencommit');
return undefined;
}
};