├── .github ├── release-drafter.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── README_CN.md ├── go.mod ├── go.sum ├── llm ├── config.go └── model.go └── main.go /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: '🐛 Bug Fixes' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - title: '📝 Documentation' 14 | labels: 15 | - 'docs' 16 | - 'documentation' 17 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 18 | template: | 19 | ## Changes 20 | 21 | $CHANGES -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release aigit 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | name: Build Binaries 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | os: [darwin, windows] 15 | arch: [amd64, arm64] 16 | exclude: 17 | - os: windows 18 | arch: arm64 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up Go 25 | uses: actions/setup-go@v4 26 | with: 27 | go-version: '1.23' 28 | 29 | - name: Get version from tag 30 | id: get_version 31 | run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 32 | 33 | - name: Build Binary 34 | env: 35 | GOOS: ${{ matrix.os }} 36 | GOARCH: ${{ matrix.arch }} 37 | run: | 38 | BINARY_NAME=aigit_${{ matrix.os }}_${{ matrix.arch }} 39 | if [ "${{ matrix.os }}" == "windows" ]; then 40 | BINARY_NAME+=".exe" 41 | fi 42 | go build -ldflags "-s -w -X main.Version=${VERSION}" -o $BINARY_NAME main.go 43 | 44 | - name: Upload Artifact 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: aigit-${{ matrix.os }}-${{ matrix.arch }} 48 | path: aigit* 49 | compression-level: 0 50 | 51 | release: 52 | needs: build 53 | runs-on: ubuntu-latest 54 | if: startsWith(github.ref, 'refs/tags/v') 55 | 56 | steps: 57 | - name: Checkout code 58 | uses: actions/checkout@v4 59 | 60 | - name: Download Artifacts 61 | uses: actions/download-artifact@v4 62 | with: 63 | path: artifacts 64 | merge-multiple: true 65 | 66 | # First, generate the release notes 67 | - name: Draft Release 68 | id: release_drafter 69 | uses: release-drafter/release-drafter@v5 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | 73 | # Then, update the release with binary files 74 | - name: Upload Release Assets 75 | uses: softprops/action-gh-release@v1 76 | if: startsWith(github.ref, 'refs/tags/') 77 | with: 78 | files: | 79 | artifacts/aigit_darwin_amd64 80 | artifacts/aigit_darwin_arm64 81 | artifacts/aigit_windows_amd64.exe 82 | draft: false 83 | body: ${{ steps.release_drafter.outputs.body }} 84 | tag_name: ${{ steps.release_drafter.outputs.tag_name }} 85 | name: ${{ steps.release_drafter.outputs.name }} 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | .idea/ 27 | 28 | 29 | build/ 30 | .DS_Store 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY_NAME=aigit 2 | VERSION=$(shell git describe --tags --always --dirty) 3 | 4 | # Build directories 5 | BUILD_DIR=build 6 | MACOS_AMD64=$(BUILD_DIR)/$(BINARY_NAME)_darwin_amd64_$(VERSION) 7 | MACOS_ARM64=$(BUILD_DIR)/$(BINARY_NAME)_darwin_arm64_$(VERSION) 8 | WINDOWS_AMD64=$(BUILD_DIR)/$(BINARY_NAME)_windows_amd64_$(VERSION).exe 9 | 10 | .PHONY: all clean build-all build-macos-amd64 build-macos-arm64 build-windows 11 | 12 | all: build-all 13 | 14 | build-all: clean macos-amd64 macos-arm64 windows 15 | @echo "Build complete! Binaries are in the $(BUILD_DIR) directory" 16 | 17 | macos-amd64: 18 | @mkdir -p $(BUILD_DIR) 19 | GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w -X main.Version=$(VERSION)" -o $(MACOS_AMD64) main.go 20 | 21 | macos-arm64: 22 | @mkdir -p $(BUILD_DIR) 23 | GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w -X main.Version=$(VERSION)" -o $(MACOS_ARM64) main.go 24 | 25 | windows: 26 | @mkdir -p $(BUILD_DIR) 27 | GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -X main.Version=$(VERSION)" -o $(WINDOWS_AMD64) main.go 28 | 29 | clean: 30 | @rm -rf $(BUILD_DIR) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aigit 2 | 3 | [中文文档 (Chinese Documentation)](./README_CN.md) | English Documentation 4 | 5 | The most powerful git commit assistant ever! 6 | 7 | It's a command-line tool that streamlines the git commit process by automatically generating meaningful and standardized commit messages, including title and body. 8 | 9 | `aigit commit` is as simple as `git commit`. 10 | 11 | ## Supported 🤖 AI Providers 12 | 13 | - [OpenAI](https://openai.com/) 14 | - [DeepSeek](https://deepseek.com/) 15 | - [Doubao (豆包)](https://www.volcengine.com/product/doubao) - Built-in, you don't need to bring your own key 16 | - [Gemini](https://gemini.google.com/) 17 | 18 | ## Getting Started 19 | 20 | ### Download the binary 21 | 22 | - Go to the [releases page](https://github.com/zzxwill/aigit/releases) and download the binary for your platform. 23 | 24 | - Rename the binary to `aigit` and move it to `/usr/local/bin/aigit`. 25 | 26 | ```shell 27 | chmod +x aigit && sudo mv aigit /usr/local/bin/aigit 28 | ``` 29 | 30 | ### Generate commit message 31 | 32 | ```shell 33 | $ aigit commit 34 | 35 | 🤖 Generating commit message... 36 | 37 | 📝 Generated commit message: 38 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 39 | feat(llm): add support for volcengine-go-sdk 40 | 41 | This commit adds support for the volcengine-go-sdk for integrating with Doubao LLM service. 42 | 43 | The following changes were made: 44 | 45 | - Provider type and APIKey field were added to the llm.Config struct. 46 | - generateDoubaoCommitMessage function was updated to use the volcengine-go-sdk. 47 | - The client is initialized with the apiKey and endpointId. 48 | - A prompt is constructed and sent to the CreateChatCompletion API. 49 | - The first choice's message is returned as the commit message. 50 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 51 | 52 | 🤔 What would you like to do? 53 | 1. Commit this message (default) 54 | 2. Regenerate message 55 | 56 | Enter your choice (press Enter for default): 57 | 58 | ✅ Successfully committed changes! 59 | ``` 60 | 61 | ### Generate commit message with your own AI API Key 62 | 63 | ```shell 64 | $ aigit auth add gemini AIzaSyCb56bjWn02e2v4s_TxHMDnHbSJQSx_tu8 65 | Successfully added API key for gemini 66 | 67 | $ aigit auth add doubao 6e3e438c-a380-4ed5-b597-e01cb82bc4df ep-20250110202503-fdkgq 68 | Successfully added API key for doubao 69 | 70 | $ aigit auth ls 71 | Configured providers: 72 | gemini *default 73 | doubao 74 | 75 | $ aigit commit 76 | 77 | 🤖 Generating commit message... 78 | 79 | 📝 Generated commit message: 80 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 81 | feat(llm): add support for volcengine-go-sdk 82 | 83 | This commit adds support for the volcengine-go-sdk for integrating with Doubao LLM service. 84 | 85 | The following changes were made: 86 | 87 | - Provider type and APIKey field were added to the llm.Config struct. 88 | - generateDoubaoCommitMessage function was updated to use the volcengine-go-sdk. 89 | - The client is initialized with the apiKey and endpointId. 90 | - A prompt is constructed and sent to the CreateChatCompletion API. 91 | - The first choice's message is returned as the commit message. 92 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 93 | 94 | 🤔 What would you like to do? 95 | 1. Commit this message (default) 96 | 2. Regenerate message 97 | 98 | Enter your choice (press Enter for default): 2 99 | 100 | 🤖 Regenerating commit message... 101 | 102 | 📝 Generated commit message: 103 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 104 | feat(llm): add support for volcengine-go-sdk 105 | 106 | This commit adds support for the volcengine-go-sdk for integrating with Doubao LLM service. 107 | 108 | The following changes were made: 109 | 110 | - Provider type and APIKey field were added to the llm.Config struct. 111 | - generateDoubaoCommitMessage function was updated to use the volcengine-go-sdk. 112 | - The client is initialized with the apiKey and endpointId. 113 | - A prompt is constructed and sent to the CreateChatCompletion API. 114 | - The first choice's message is returned as the commit message. 115 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 116 | 117 | 🤔 What would you like to do? 118 | 1. Commit this message (default) 119 | 2. Regenerate message 120 | 121 | Enter your choice (press Enter for default): 1 122 | 123 | ✅ Successfully committed changes! 124 | 125 | ``` 126 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # aigit 2 | 3 | [English Documentation](README.md) | 中文文档 4 | 5 | 最强大的 Git 提交助手! 6 | 7 | ## 支持的大模型/AI 8 | 9 | - [OpenAI](https://openai.com/) 10 | - [DeepSeek](https://deepseek.com/) 11 | - [Doubao (豆包)](https://www.volcengine.com/product/doubao) - 内置,您不需要自己携带 API Key 12 | - [Gemini](https://gemini.google.com/) 13 | 14 | ## 快速开始 15 | 16 | ### 下载二进制文件 17 | 18 | - 前往 [发布页面](https://github.com/zzxwill/aigit/releases) 下载适合您平台的二进制文件。 19 | 20 | - 将二进制文件重命名为 `aigit` 并移动到 `/usr/local/bin/aigit`。 21 | 22 | ```shell 23 | chmod +x aigit && sudo mv aigit /usr/local/bin/aigit 24 | ``` 25 | 26 | ### 生成提交信息 27 | 28 | ```shell 29 | $ aigit commit 30 | 31 | 🤖 Generating commit message... 32 | 33 | 📝 Generated commit message: 34 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 35 | feat(llm): add support for volcengine-go-sdk 36 | 37 | This commit adds support for the volcengine-go-sdk for integrating with Doubao LLM service. 38 | 39 | The following changes were made: 40 | 41 | - Provider type and APIKey field were added to the llm.Config struct. 42 | - generateDoubaoCommitMessage function was updated to use the volcengine-go-sdk. 43 | - The client is initialized with the apiKey and endpointId. 44 | - A prompt is constructed and sent to the CreateChatCompletion API. 45 | - The first choice's message is returned as the commit message. 46 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 47 | 48 | 🤔 What would you like to do? 49 | 1. Commit this message (default) 50 | 2. Regenerate message 51 | 52 | Enter your choice (press Enter for default): 53 | 54 | ✅ Successfully committed changes! 55 | ``` 56 | 57 | ### 使用自己的 AI API Key 生成提交信息 58 | 59 | ```shell 60 | $ aigit auth add gemini AIzaSyCb56bjWn02e2v4s_TxHMDnHbSJQSx_tu8 61 | Successfully added API key for gemini 62 | 63 | $ aigit auth add doubao 6e3e438c-a380-4ed5-b597-e01cb82bc4df ep-20250110202503-fdkgq 64 | Successfully added API key for doubao 65 | 66 | $ aigit auth ls 67 | Configured providers: 68 | gemini *default 69 | doubao 70 | 71 | $ aigit commit 72 | 73 | 🤖 Generating commit message... 74 | 75 | 📝 Generated commit message: 76 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 77 | feat(llm): add support for volcengine-go-sdk 78 | 79 | This commit adds support for the volcengine-go-sdk for integrating with Doubao LLM service. 80 | 81 | The following changes were made: 82 | 83 | - Provider type and APIKey field were added to the llm.Config struct. 84 | - generateDoubaoCommitMessage function was updated to use the volcengine-go-sdk. 85 | - The client is initialized with the apiKey and endpointId. 86 | - A prompt is constructed and sent to the CreateChatCompletion API. 87 | - The first choice's message is returned as the commit message. 88 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89 | 90 | 🤔 What would you like to do? 91 | 1. Commit this message (default) 92 | 2. Regenerate message 93 | 94 | Enter your choice (press Enter for default): 2 95 | 96 | 🤖 Regenerating commit message... 97 | 98 | 📝 Generated commit message: 99 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100 | feat(llm): add support for volcengine-go-sdk 101 | 102 | This commit adds support for the volcengine-go-sdk for integrating with Doubao LLM service. 103 | 104 | The following changes were made: 105 | 106 | - Provider type and APIKey field were added to the llm.Config struct. 107 | - generateDoubaoCommitMessage function was updated to use the volcengine-go-sdk. 108 | - The client is initialized with the apiKey and endpointId. 109 | - A prompt is constructed and sent to the CreateChatCompletion API. 110 | - The first choice's message is returned as the commit message. 111 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 112 | 113 | 🤔 What would you like to do? 114 | 1. Commit this message (default) 115 | 2. Regenerate message 116 | 117 | Enter your choice (press Enter for default): 1 118 | 119 | ✅ Successfully committed changes! 120 | 121 | ``` 122 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zzxwill/aigit 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/fatih/color v1.18.0 7 | github.com/google/generative-ai-go v0.19.0 8 | github.com/manifoldco/promptui v0.9.0 9 | github.com/openai/openai-go v0.1.0-alpha.51 10 | github.com/spf13/cobra v1.8.1 11 | github.com/volcengine/volcengine-go-sdk v1.0.177 12 | google.golang.org/api v0.215.0 13 | ) 14 | 15 | require ( 16 | cloud.google.com/go v0.115.0 // indirect 17 | cloud.google.com/go/ai v0.8.0 // indirect 18 | cloud.google.com/go/auth v0.13.0 // indirect 19 | cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect 20 | cloud.google.com/go/compute/metadata v0.6.0 // indirect 21 | cloud.google.com/go/longrunning v0.5.7 // indirect 22 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect 23 | github.com/felixge/httpsnoop v1.0.4 // indirect 24 | github.com/go-logr/logr v1.4.2 // indirect 25 | github.com/go-logr/stdr v1.2.2 // indirect 26 | github.com/google/s2a-go v0.1.8 // indirect 27 | github.com/google/uuid v1.6.0 // indirect 28 | github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect 29 | github.com/googleapis/gax-go/v2 v2.14.1 // indirect 30 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 31 | github.com/jmespath/go-jmespath v0.4.0 // indirect 32 | github.com/mattn/go-colorable v0.1.13 // indirect 33 | github.com/mattn/go-isatty v0.0.20 // indirect 34 | github.com/spf13/pflag v1.0.5 // indirect 35 | github.com/tidwall/gjson v1.14.4 // indirect 36 | github.com/tidwall/match v1.1.1 // indirect 37 | github.com/tidwall/pretty v1.2.1 // indirect 38 | github.com/tidwall/sjson v1.2.5 // indirect 39 | github.com/volcengine/volc-sdk-golang v1.0.23 // indirect 40 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect 41 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect 42 | go.opentelemetry.io/otel v1.29.0 // indirect 43 | go.opentelemetry.io/otel/metric v1.29.0 // indirect 44 | go.opentelemetry.io/otel/trace v1.29.0 // indirect 45 | golang.org/x/crypto v0.31.0 // indirect 46 | golang.org/x/net v0.33.0 // indirect 47 | golang.org/x/oauth2 v0.24.0 // indirect 48 | golang.org/x/sync v0.10.0 // indirect 49 | golang.org/x/sys v0.28.0 // indirect 50 | golang.org/x/text v0.21.0 // indirect 51 | golang.org/x/time v0.8.0 // indirect 52 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect 53 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect 54 | google.golang.org/grpc v1.67.1 // indirect 55 | google.golang.org/protobuf v1.36.1 // indirect 56 | gopkg.in/yaml.v2 v2.2.8 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= 3 | cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= 4 | cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= 5 | cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= 6 | cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= 7 | cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= 8 | cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= 9 | cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= 10 | cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= 11 | cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= 12 | cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= 13 | cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= 16 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 17 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 18 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 19 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 20 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 21 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 22 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 23 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 24 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 27 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 29 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 30 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 31 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 32 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 33 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 34 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 35 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 36 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 37 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 38 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 39 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 40 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 41 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 42 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 44 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 45 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 46 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 47 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 48 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 49 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 50 | github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg= 51 | github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= 52 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 53 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 54 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 55 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 57 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 58 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 59 | github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= 60 | github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= 61 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 62 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 63 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 64 | github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= 65 | github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= 66 | github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= 67 | github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= 68 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 69 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 70 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 71 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 72 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 73 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 74 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 75 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 76 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 77 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 78 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 79 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= 80 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= 81 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 82 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 83 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 84 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 85 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 86 | github.com/openai/openai-go v0.1.0-alpha.51 h1:/iuF8QoWt4x9yoEr6AdMsSBc2SglamxA/a7wClrDrqw= 87 | github.com/openai/openai-go v0.1.0-alpha.51/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A= 88 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 89 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 90 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 91 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 92 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 93 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 94 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 95 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 96 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 97 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 98 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 99 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 100 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 101 | github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 102 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 103 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 104 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 105 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 106 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 107 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 108 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 109 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= 110 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= 111 | github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8= 112 | github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU= 113 | github.com/volcengine/volcengine-go-sdk v1.0.177 h1:Z5D8BZAR1ilH7bLtRjBVP/I0QOIk7G/xuLvjeSJIax0= 114 | github.com/volcengine/volcengine-go-sdk v1.0.177/go.mod h1:gfEDc1s7SYaGoY+WH2dRrS3qiuDJMkwqyfXWCa7+7oA= 115 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= 116 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= 117 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= 118 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= 119 | go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 120 | go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 121 | go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= 122 | go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= 123 | go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= 124 | go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= 125 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 126 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 127 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 128 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 129 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 130 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 131 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 132 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 133 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 134 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 135 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 136 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 137 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 138 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 139 | golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= 140 | golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 141 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 143 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 144 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 145 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 146 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 147 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 148 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 149 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 150 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 151 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 152 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 153 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 154 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 155 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 156 | golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 157 | golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 158 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 159 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 160 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 161 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 162 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 163 | google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0= 164 | google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= 165 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 166 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 167 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 168 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 169 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 170 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= 171 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= 172 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= 173 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= 174 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 175 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 176 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 177 | google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= 178 | google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 179 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 180 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 181 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 182 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 183 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 184 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 185 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 186 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 187 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 188 | google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= 189 | google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 190 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 191 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 192 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 193 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 194 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 195 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 196 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 197 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 198 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 199 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 200 | -------------------------------------------------------------------------------- /llm/config.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | type Provider struct { 11 | APIKey string `json:"api_key"` 12 | Endpoint string `json:"endpoint"` 13 | } 14 | 15 | type Config struct { 16 | CurrentProvider string `json:"current_provider"` 17 | Providers map[string]Provider `json:"providers"` 18 | } 19 | 20 | func NewConfig() *Config { 21 | return &Config{ 22 | Providers: make(map[string]Provider), 23 | } 24 | } 25 | 26 | func (c *Config) Load() error { 27 | homeDir, err := os.UserHomeDir() 28 | if err != nil { 29 | return fmt.Errorf("getting home directory: %w", err) 30 | } 31 | 32 | configFile := filepath.Join(homeDir, ".aigit", "config.json") 33 | configData, err := os.ReadFile(configFile) 34 | if err != nil { 35 | if os.IsNotExist(err) { 36 | return nil // Return empty config if file doesn't exist 37 | } 38 | return fmt.Errorf("reading config file: %w", err) 39 | } 40 | 41 | if err := json.Unmarshal(configData, c); err != nil { 42 | return fmt.Errorf("parsing config file: %w", err) 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func (c *Config) Save() error { 49 | homeDir, err := os.UserHomeDir() 50 | if err != nil { 51 | return fmt.Errorf("getting home directory: %w", err) 52 | } 53 | 54 | configDir := filepath.Join(homeDir, ".aigit") 55 | if err := os.MkdirAll(configDir, 0700); err != nil { 56 | return fmt.Errorf("creating config directory: %w", err) 57 | } 58 | 59 | configFile := filepath.Join(configDir, "config.json") 60 | jsonData, err := json.MarshalIndent(c, "", " ") 61 | if err != nil { 62 | return fmt.Errorf("encoding config: %w", err) 63 | } 64 | 65 | if err := os.WriteFile(configFile, jsonData, 0600); err != nil { 66 | return fmt.Errorf("saving config: %w", err) 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func (c *Config) AddProvider(provider, apiKey string, endpoint ...string) error { 73 | switch provider { 74 | case ProviderDoubao: 75 | c.Providers[provider] = Provider{ 76 | APIKey: apiKey, 77 | Endpoint: endpoint[0], 78 | } 79 | default: 80 | c.Providers[provider] = Provider{ 81 | APIKey: apiKey, 82 | } 83 | } 84 | if c.CurrentProvider == "" { 85 | c.CurrentProvider = provider 86 | } 87 | return c.Save() 88 | } 89 | 90 | func (c *Config) UseProvider(provider string) error { 91 | if _, exists := c.Providers[provider]; !exists { 92 | return fmt.Errorf("provider %s not configured", provider) 93 | } 94 | c.CurrentProvider = provider 95 | return c.Save() 96 | } 97 | 98 | func (c *Config) GetAPIKey(provider string) (string, error) { 99 | if p, exists := c.Providers[provider]; exists { 100 | return p.APIKey, nil 101 | } 102 | return "", fmt.Errorf("no API key found for provider %s", provider) 103 | } 104 | 105 | func (c *Config) ListProviders() []string { 106 | providers := make([]string, 0, len(c.Providers)) 107 | for k, _ := range c.Providers { 108 | if k == c.CurrentProvider { 109 | providers = append(providers, k+" *default") 110 | } else { 111 | providers = append(providers, k) 112 | } 113 | } 114 | return providers 115 | } 116 | -------------------------------------------------------------------------------- /llm/model.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "net/http" 9 | "strings" 10 | 11 | "github.com/google/generative-ai-go/genai" 12 | "github.com/openai/openai-go" 13 | openaioption "github.com/openai/openai-go/option" 14 | "github.com/volcengine/volcengine-go-sdk/service/arkruntime" 15 | "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model" 16 | "github.com/volcengine/volcengine-go-sdk/volcengine" 17 | "google.golang.org/api/option" 18 | ) 19 | 20 | const ( 21 | ProviderOpenAI = "openai" 22 | ProviderGemini = "gemini" 23 | ProviderDoubao = "doubao" 24 | ProviderDeepseek = "deepseek" 25 | 26 | // Model constants 27 | geminiModel = "gemini-pro" 28 | deepseekModel = "deepseek-chat" 29 | openaiModel = "chatgpt-4o-latest" 30 | ) 31 | 32 | const ( 33 | DefaultApiKey = "YzAzZDIyN2ItMWFkNS00MDNkLWJkM2YtZjgzNzczOWE4YzFj" 34 | DefaultEndpoint = "ZXAtMjAyNTAxMTMyMzE5NTEtOTJ4bjI=" 35 | ) 36 | 37 | const llmPrompt = `Generate a concise and informative Git commit message based on the following code diff. 38 | 39 | The commit message should follow these rules: 40 | 1. Follow the Conventional Commits format: (): 41 | 2. The body should be one paragraph 42 | 3. The body should explain WHAT and WHY (not HOW) 43 | 4. Each line should be less than 72 characters 44 | 5. There should be a line break between the title and the body 45 | 6. Disable markdown formatting 46 | 47 | Here's the diff:` 48 | 49 | func GenerateGeminiCommitMessage(diff, apiKey string) (string, error) { 50 | ctx := context.Background() 51 | client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) 52 | if err != nil { 53 | return "", fmt.Errorf("creating Gemini client: %w", err) 54 | } 55 | defer client.Close() 56 | 57 | model := client.GenerativeModel(geminiModel) 58 | prompt := fmt.Sprintf("%s\n%s", llmPrompt, diff) 59 | 60 | resp, err := model.GenerateContent(ctx, genai.Text(prompt)) 61 | if err != nil { 62 | return "", fmt.Errorf("generating commit message: %w", err) 63 | } 64 | 65 | if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 { 66 | generatedMessage := resp.Candidates[0].Content.Parts[0].(genai.Text) 67 | return strings.TrimSpace(string(generatedMessage)), nil 68 | } 69 | 70 | return "", fmt.Errorf("no commit message generated by Gemini") 71 | } 72 | 73 | func GenerateOpenAICommitMessage(diff, apiKey string) (string, error) { 74 | client := openai.NewClient( 75 | openaioption.WithAPIKey(apiKey), 76 | ) 77 | ctx := context.Background() 78 | prompt := fmt.Sprintf("%s\n%s", llmPrompt, diff) 79 | 80 | chatCompletion, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{ 81 | Messages: openai.F([]openai.ChatCompletionMessageParamUnion{ 82 | openai.UserMessage(prompt), 83 | }), 84 | Model: openai.F(openai.ChatModelGPT4o), 85 | }) 86 | if err != nil { 87 | return "", fmt.Errorf("generating commit message: %w", err) 88 | } 89 | 90 | if len(chatCompletion.Choices) > 0 { 91 | return strings.TrimSpace(chatCompletion.Choices[0].Message.Content), nil 92 | } 93 | 94 | return "", fmt.Errorf("no commit message generated by OpenAI") 95 | } 96 | 97 | func GenerateDoubaoCommitMessage(diff, apiKey string, endpointId string) (string, error) { 98 | client := arkruntime.NewClientWithApiKey( 99 | apiKey, 100 | ) 101 | 102 | ctx := context.Background() 103 | 104 | prompt := fmt.Sprintf("%s\n%s", llmPrompt, diff) 105 | 106 | req := model.ChatCompletionRequest{ 107 | Model: endpointId, 108 | Messages: []*model.ChatCompletionMessage{ 109 | { 110 | Role: model.ChatMessageRoleSystem, 111 | Content: &model.ChatCompletionMessageContent{ 112 | StringValue: volcengine.String("你是豆包,是由字节跳动开发的 AI 人工智能助手, 你非常擅长生成 git commit message"), 113 | }, 114 | }, 115 | { 116 | Role: model.ChatMessageRoleUser, 117 | Content: &model.ChatCompletionMessageContent{ 118 | StringValue: volcengine.String(prompt), 119 | }, 120 | }, 121 | }, 122 | } 123 | 124 | resp, err := client.CreateChatCompletion(ctx, req) 125 | if err != nil { 126 | return "", err 127 | } 128 | return *resp.Choices[0].Message.Content.StringValue, nil 129 | } 130 | 131 | func GenerateDeepseekCommitMessage(diff, apiKey string) (string, error) { 132 | client := &http.Client{} 133 | ctx := context.Background() 134 | prompt := fmt.Sprintf("%s\n%s", llmPrompt, diff) 135 | 136 | reqBody := map[string]interface{}{ 137 | "model": deepseekModel, 138 | "messages": []map[string]string{ 139 | { 140 | "role": "system", 141 | "content": "You are Deepseek, an AI assistant specialized in generating git commit messages.", 142 | }, 143 | { 144 | "role": "user", 145 | "content": prompt, 146 | }, 147 | }, 148 | "stream": false, 149 | } 150 | 151 | jsonBody, err := json.Marshal(reqBody) 152 | if err != nil { 153 | return "", fmt.Errorf("marshaling request body: %w", err) 154 | } 155 | 156 | req, err := http.NewRequestWithContext(ctx, "POST", "https://api.deepseek.com/chat/completions", bytes.NewBuffer(jsonBody)) 157 | if err != nil { 158 | return "", fmt.Errorf("creating request: %w", err) 159 | } 160 | 161 | req.Header.Set("Content-Type", "application/json") 162 | req.Header.Set("Authorization", "Bearer "+apiKey) 163 | 164 | resp, err := client.Do(req) 165 | if err != nil { 166 | return "", fmt.Errorf("making request: %w", err) 167 | } 168 | defer resp.Body.Close() 169 | 170 | var result map[string]interface{} 171 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 172 | return "", fmt.Errorf("decoding response: %w", err) 173 | } 174 | 175 | if choices, ok := result["choices"].([]interface{}); ok && len(choices) > 0 { 176 | if choice, ok := choices[0].(map[string]interface{}); ok { 177 | if message, ok := choice["message"].(map[string]interface{}); ok { 178 | if content, ok := message["content"].(string); ok { 179 | return content, nil 180 | } 181 | } 182 | } 183 | } 184 | 185 | return "", fmt.Errorf("invalid response format from Deepseek: %v", result) 186 | } 187 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | 10 | "github.com/fatih/color" 11 | "github.com/manifoldco/promptui" 12 | "github.com/spf13/cobra" 13 | "github.com/zzxwill/aigit/llm" 14 | ) 15 | 16 | var Version = "dev" 17 | 18 | func main() { 19 | var rootCmd = &cobra.Command{ 20 | Use: "aigit", 21 | Short: "Generate git commit message including title and body", 22 | Long: `AI Git Commi streamlines the git commit process by automatically generating meaningful and standardized commit messages.`, 23 | } 24 | 25 | var authCmd = &cobra.Command{ 26 | Use: "auth", 27 | Short: "Manage LLM providers and API keys", 28 | Long: `Manage Language Model providers and their API keys. Use subcommands to list, add, or select providers.`, 29 | DisableFlagsInUseLine: true, 30 | } 31 | 32 | var authListCmd = &cobra.Command{ 33 | Use: "list", 34 | Aliases: []string{"ls"}, 35 | Short: "List configured LLM providers", 36 | DisableFlagsInUseLine: true, 37 | Run: func(cmd *cobra.Command, args []string) { 38 | config := llm.NewConfig() 39 | if err := config.Load(); err != nil { 40 | fmt.Printf("Error reading config: %v\n", err) 41 | os.Exit(1) 42 | } 43 | 44 | fmt.Println("Configured providers:") 45 | for _, provider := range config.ListProviders() { 46 | if provider == config.CurrentProvider { 47 | fmt.Printf("* %s (current)\n", provider) 48 | } else { 49 | fmt.Printf(" %s\n", provider) 50 | } 51 | } 52 | }, 53 | } 54 | 55 | var authAddCmd = &cobra.Command{ 56 | Use: "add [endpoint_id]", 57 | Short: "Add or update API key for a provider", 58 | Long: "Add or update API key for a provider. Supported providers: openai, gemini, doubao, deepseek. endpoint_id is required for Doubao provider", 59 | DisableFlagsInUseLine: true, 60 | Run: func(cmd *cobra.Command, args []string) { 61 | if len(args) < 2 { 62 | color.Red("Not enough arguments") 63 | color.Red(cmd.Long) 64 | color.Red("\nUsage: aigit auth add [endpoint_id]") 65 | os.Exit(1) 66 | } 67 | 68 | provider := strings.ToLower(args[0]) 69 | apiKey := args[1] 70 | 71 | config := llm.NewConfig() 72 | if err := config.Load(); err != nil { 73 | fmt.Printf("Error reading config: %v\n", err) 74 | os.Exit(1) 75 | } 76 | 77 | // Validate provider 78 | switch provider { 79 | case llm.ProviderOpenAI, llm.ProviderGemini, llm.ProviderDeepseek: 80 | if err := config.AddProvider(provider, apiKey); err != nil { 81 | fmt.Printf("Error saving config: %v\n", err) 82 | os.Exit(1) 83 | } 84 | case llm.ProviderDoubao: 85 | if len(args) < 3 { 86 | color.Red("Endpoint ID is required for Doubao provider") 87 | color.Red("Please run `aigit auth add doubao `") 88 | os.Exit(1) 89 | } 90 | if err := config.AddProvider(provider, apiKey, args[2]); err != nil { 91 | fmt.Printf("Error saving config: %v\n", err) 92 | os.Exit(1) 93 | } 94 | default: 95 | fmt.Printf("Unsupported provider: %s\nSupported providers are: openai, gemini, doubao, deepseek\n", provider) 96 | os.Exit(1) 97 | } 98 | 99 | color.Green("Successfully added API key for %s", provider) 100 | }, 101 | } 102 | 103 | var authUseCmd = &cobra.Command{ 104 | Use: "use [provider]", 105 | Short: "Set the current LLM provider", 106 | Args: cobra.ExactArgs(1), 107 | DisableFlagsInUseLine: true, 108 | Run: func(cmd *cobra.Command, args []string) { 109 | provider := strings.ToLower(args[0]) 110 | 111 | config := llm.NewConfig() 112 | if err := config.Load(); err != nil { 113 | fmt.Printf("Error reading config: %v\n", err) 114 | os.Exit(1) 115 | } 116 | 117 | if err := config.UseProvider(provider); err != nil { 118 | fmt.Printf("Error: %v\n", err) 119 | os.Exit(1) 120 | } 121 | 122 | color.Green("Now using %s as the current provider", provider) 123 | }, 124 | } 125 | 126 | authCmd.AddCommand(authListCmd) 127 | authCmd.AddCommand(authAddCmd) 128 | authCmd.AddCommand(authUseCmd) 129 | rootCmd.AddCommand(authCmd) 130 | 131 | var commitCmd = &cobra.Command{ 132 | Use: "commit", 133 | Short: "Generate git commit message including title and body", 134 | Run: func(cmd *cobra.Command, args []string) { 135 | // Execute git diff --cached command 136 | diffOutput, err := exec.Command("git", "diff", "--cached").Output() 137 | if err != nil { 138 | fmt.Printf("Error getting git diff: %v\n", err) 139 | os.Exit(1) 140 | } 141 | 142 | // If there are no staged changes 143 | if len(diffOutput) == 0 { 144 | color.Yellow("No staged changes found.") 145 | stagePrompt := promptui.Select{ 146 | Label: "Would you like to run 'git add .' to stage all changes?", 147 | Items: []string{"Yes", "No"}, 148 | Size: 2, 149 | } 150 | 151 | _, stageChoice, err := stagePrompt.Run() 152 | if err != nil { 153 | fmt.Printf("Error with prompt: %v\n", err) 154 | os.Exit(1) 155 | } 156 | 157 | if stageChoice == "Yes" { 158 | cmd := exec.Command("git", "add", ".") 159 | if err := cmd.Run(); err != nil { 160 | fmt.Printf("Error staging changes: %v\n", err) 161 | os.Exit(1) 162 | } 163 | color.Green("All changes staged successfully!") 164 | 165 | // Re-run git diff to get the newly staged changes 166 | diffOutput, err = exec.Command("git", "diff", "--cached").Output() 167 | if err != nil { 168 | fmt.Printf("Error getting git diff: %v\n", err) 169 | os.Exit(1) 170 | } 171 | } else { 172 | color.Red("No changes staged. Please use 'git add' to stage your changes.") 173 | os.Exit(1) 174 | } 175 | } 176 | 177 | config := llm.NewConfig() 178 | if err := config.Load(); err != nil { 179 | fmt.Printf("Error reading config: %v\n", err) 180 | os.Exit(1) 181 | } 182 | 183 | var provider string 184 | if config.CurrentProvider == "" { 185 | provider = llm.ProviderDoubao 186 | } else { 187 | provider = config.CurrentProvider 188 | } 189 | 190 | // First message generation 191 | fmt.Println("\n🤖 Generating commit message by", provider) 192 | var commitMessage string 193 | commitMessage, err = generateMessage(config, diffOutput) 194 | if err != nil { 195 | fmt.Printf("Error generating commit message: %v\n", err) 196 | os.Exit(1) 197 | } 198 | 199 | for { 200 | // Clear some space and show the message in a box 201 | fmt.Println("\n📝 Generated commit message:") 202 | fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") 203 | fmt.Println(commitMessage) 204 | fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") 205 | 206 | fmt.Println("\n🤔 What would you like to do?") 207 | prompt := promptui.Select{ 208 | Label: "Choose an action", 209 | Items: []string{"Commit this message", "Regenerate message"}, 210 | Size: 2, 211 | } 212 | 213 | commitChoice, _, err := prompt.Run() 214 | if err != nil { 215 | fmt.Printf("Error with prompt: %v\n", err) 216 | os.Exit(1) 217 | } 218 | 219 | switch commitChoice { 220 | case 0: 221 | cmd := exec.Command("git", "commit", "-m", commitMessage) 222 | if err := cmd.Run(); err != nil { 223 | fmt.Printf("Error committing changes: %v\n", err) 224 | os.Exit(1) 225 | } 226 | color.Green("\n✅ Successfully committed changes!") 227 | 228 | pushPrompt := promptui.Select{ 229 | Label: "Would you like to push these changes to the remote repository?", 230 | Items: []string{"No", "Yes"}, 231 | Size: 2, 232 | } 233 | 234 | _, pushChoice, err := pushPrompt.Run() 235 | if err != nil { 236 | fmt.Printf("Error with prompt: %v\n", err) 237 | os.Exit(1) 238 | } 239 | 240 | if pushChoice == "Yes" { 241 | cmd := exec.Command("git", "push") 242 | output, err := cmd.CombinedOutput() 243 | if err != nil { 244 | color.Red("Error pushing changes: %v\n%s", err, output) 245 | os.Exit(1) 246 | } 247 | fmt.Printf("%s", output) 248 | color.Green("✅ Successfully pushed changes to remote repository!") 249 | } else { 250 | color.Yellow("Changes committed locally. Remember to push when ready.") 251 | } 252 | return 253 | case 1: 254 | fmt.Println("\n🤖 Regenerating commit message...") 255 | commitMessage, err = generateMessage(config, diffOutput) 256 | if err != nil { 257 | fmt.Printf("Error generating commit message: %v\n", err) 258 | os.Exit(1) 259 | } 260 | continue 261 | default: 262 | color.Red("Invalid choice") 263 | } 264 | } 265 | }, 266 | } 267 | 268 | rootCmd.AddCommand(commitCmd) 269 | 270 | var versionCmd = &cobra.Command{ 271 | Use: "version", 272 | Aliases: []string{"v", "-v", "-version", "--version"}, 273 | Short: "Print the version of aigit", 274 | Long: "Print the current version of the aigit CLI tool.", 275 | Run: func(cmd *cobra.Command, args []string) { 276 | if Version == "dev" { 277 | version, err := exec.Command("git", "describe", "--tags").Output() 278 | if err != nil { 279 | fmt.Printf("Error retrieving version: %v\n", err) 280 | os.Exit(1) 281 | } 282 | fmt.Printf("%s\n", strings.TrimSpace(string(version))) 283 | } else { 284 | fmt.Printf("%s\n", Version) 285 | } 286 | }, 287 | } 288 | 289 | rootCmd.AddCommand(versionCmd) 290 | 291 | if err := rootCmd.Execute(); err != nil { 292 | fmt.Println(err) 293 | os.Exit(1) 294 | } 295 | } 296 | 297 | // Add this helper function before the main function 298 | func generateMessage(config *llm.Config, diffOutput []byte) (string, error) { 299 | var commitMessage string 300 | var err error 301 | 302 | apiKey := config.Providers[config.CurrentProvider].APIKey 303 | 304 | switch config.CurrentProvider { 305 | case llm.ProviderGemini: 306 | commitMessage, err = llm.GenerateGeminiCommitMessage(string(diffOutput), apiKey) 307 | case llm.ProviderDoubao: 308 | endpoint := config.Providers[config.CurrentProvider].Endpoint 309 | commitMessage, err = llm.GenerateDoubaoCommitMessage(string(diffOutput), apiKey, endpoint) 310 | case llm.ProviderOpenAI: 311 | commitMessage, err = llm.GenerateOpenAICommitMessage(string(diffOutput), apiKey) 312 | case llm.ProviderDeepseek: 313 | commitMessage, err = llm.GenerateDeepseekCommitMessage(string(diffOutput), apiKey) 314 | default: 315 | apiKey, err1 := base64.StdEncoding.DecodeString(llm.DefaultApiKey) 316 | if err1 != nil { 317 | return "", fmt.Errorf("error decoding API key: %v", err1) 318 | } 319 | endpoint, err1 := base64.StdEncoding.DecodeString(llm.DefaultEndpoint) 320 | if err1 != nil { 321 | return "", fmt.Errorf("error decoding endpoint: %v", err1) 322 | } 323 | commitMessage, err = llm.GenerateDoubaoCommitMessage(string(diffOutput), string(apiKey), string(endpoint)) 324 | } 325 | return commitMessage, err 326 | } 327 | --------------------------------------------------------------------------------