├── .babelrc ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── bin └── clog-ai ├── package.json ├── src ├── index.ts └── template │ ├── template.en.md │ └── template.zh.md ├── tea.yaml ├── test └── blah.test.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "babel-plugin-inline-import", 5 | { 6 | "extensions": [".md"] 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | .idea 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "always", 11 | "vueIndentScriptAndStyle": true, 12 | "endOfLine": "auto" 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 yutou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clog-ai 2 | 3 | 使用 AI 自动生成 Commit Log 4 | 5 | Generate commit log with AI 6 | 7 | ## Example 8 | 9 | 示例可参照本项目的 commit log 10 | 11 | You can see the commit log of this project 12 | 13 | ## Installation 14 | 15 | ```bash 16 | npm install -g clog-ai 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```bash 22 | clog-ai init 23 | ``` 24 | 25 | then edit config file: 26 | 27 | ``` 28 | { 29 | "language": "zh or en", 30 | "datasource": "openai or azure", 31 | "openai_api_key": "xxxxx", 32 | "azure_api_key": "xxxxx", 33 | "azure_deployment_id": "xxxx", 34 | "azure_base_url": "https://xxxxxxx.openai.azure.com", 35 | "azure_model": "gpt-3.5-turbo-16k", 36 | "azure_api_version": "2023-07-01-preview" 37 | } 38 | ``` 39 | 40 | ## Run 41 | 42 | ```bash 43 | git add . # git add files 44 | 45 | clog-ai # generate commit log only 46 | 47 | clog-ai --verbose # generate with prompt display 48 | ``` 49 | 50 | ![image](https://github.com/aoao-eth/AI-Commit-Log/assets/897401/36b50dc9-5846-424c-a2da-b5149397e1ba) 51 | 52 | ## Azure api 53 | 54 | https://portal.azure.com/ 55 | 56 | 首先免费申请 openai 服务,然后部署 gpt-3.5-turbo-16k 模型,最终将相关的配置写入配置文件 57 | 58 | First, apply for openai service for free, then deploy the gpt-3.5-turbo-16k model, and finally write the relevant configuration into the configuration file 59 | 60 | ## OpenAI api 61 | 62 | https://platform.openai.com/api-keys 63 | 64 | 申请 OpenAI API key,将其写入配置文件中 65 | 66 | Apply for an OpenAI API key and write it into the configuration file. 67 | 68 | 69 | -------------------------------------------------------------------------------- /bin/clog-ai: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const run = require('../dist/index.js').default; 4 | 5 | run(); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.19", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "bin": { 11 | "clog-ai": "bin/clog-ai" 12 | }, 13 | "engines": { 14 | "node": ">=10" 15 | }, 16 | "scripts": { 17 | "start": "tsdx watch", 18 | "build": "tsdx build", 19 | "test": "tsdx test", 20 | "lint": "tsdx lint", 21 | "prepare": "tsdx build", 22 | "size": "size-limit", 23 | "analyze": "size-limit --why" 24 | }, 25 | "peerDependencies": {}, 26 | "husky": { 27 | "hooks": { 28 | "pre-commit": "tsdx lint" 29 | } 30 | }, 31 | "prettier": { 32 | "printWidth": 80, 33 | "semi": true, 34 | "singleQuote": true, 35 | "trailingComma": "es5" 36 | }, 37 | "name": "clog-ai", 38 | "author": "yutou", 39 | "description": "Generate commit log with AI", 40 | "keywords": [ 41 | "commit", 42 | "log", 43 | "ai", 44 | "clog", 45 | "gpt" 46 | ], 47 | "repository": { 48 | "type": "github", 49 | "url": "https://github.com/aoao-eth/AI-Commit-Log" 50 | }, 51 | "module": "dist/clog-ai.esm.js", 52 | "size-limit": [ 53 | { 54 | "path": "dist/clog-ai.cjs.production.min.js", 55 | "limit": "10 KB" 56 | }, 57 | { 58 | "path": "dist/clog-ai.esm.js", 59 | "limit": "10 KB" 60 | } 61 | ], 62 | "dependencies": { 63 | "axios": "^1.6.2", 64 | "babel-plugin-inline-import": "^3.0.0", 65 | "execa": "^8.0.1", 66 | "loading-cli": "^1.1.2", 67 | "prettier": "^3.1.1", 68 | "tsdx": "^0.14.1" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import child_process from 'child_process'; 3 | import loading from 'loading-cli'; 4 | import * as fs from 'fs'; 5 | import * as path from 'path'; 6 | import * as os from 'os'; 7 | import readline from 'readline'; 8 | //@ts-ignore 9 | import TEMPLACE_CN from './template/template.zh.md'; 10 | //@ts-ignore 11 | import TEMPLACE_EN from './template/template.en.md'; 12 | 13 | const homeDir = os.homedir(); 14 | 15 | const configFilePath = path.join(homeDir, '.config/clog-ai/config.json'); 16 | 17 | const command = process.argv[2]?.trim(); 18 | 19 | const isVerbose = process.argv.includes('--verbose') || process.argv.includes('-v'); 20 | 21 | if (command == 'init') { 22 | if (fs.existsSync(configFilePath)) { 23 | console.log( 24 | 'config file already exists, please edit config file: ' + 25 | path.resolve(configFilePath) 26 | ); 27 | process.exit(0); 28 | } 29 | const config = { 30 | language: 'zh', 31 | datasource: 'openai', 32 | openai_api_key: '', 33 | openai_base_url: 'https://api.openai.com', 34 | openai_model: 'gpt-3.5-turbo-16k', 35 | azure_api_key: '', 36 | azure_deployment_id: '', 37 | azure_base_url: '', 38 | azure_model: '', 39 | azure_api_version: '', 40 | }; 41 | const dirPath = path.dirname(configFilePath); 42 | 43 | if (!fs.existsSync(dirPath)) { 44 | fs.mkdirSync(dirPath, { recursive: true }); 45 | } 46 | fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2)); 47 | console.log( 48 | 'init success, please edit config file: ' + path.resolve(configFilePath) 49 | ); 50 | 51 | process.exit(0); 52 | } 53 | 54 | if (!fs.existsSync(configFilePath)) { 55 | console.log('please run this command first: clog-ai init'); 56 | process.exit(0); 57 | } 58 | 59 | const config = getConfig() 60 | //valid_datasource: azure openai 61 | const valid_datasource = ['azure', 'openai'] 62 | 63 | if(!config.datasource){ 64 | console.log('Please specify the datasource in the configuration file: ' + path.resolve(configFilePath)); 65 | process.exit(0); 66 | } 67 | 68 | if(!valid_datasource.includes(config.datasource)){ 69 | console.log('Invalid datasource. Please check the configuration file: ' + path.resolve(configFilePath)); 70 | process.exit(0); 71 | } 72 | 73 | if(config.datasource === 'openai' && (!config.openai_api_key)){ 74 | console.log('Data source is OpenAI, but the corresponding configuration is missing. Please add it in the configuration file: ' + path.resolve(configFilePath)) 75 | process.exit(0); 76 | } 77 | 78 | if (config.datasource === 'azure' && (!config.azure_api_key || !config.azure_deployment_id || !config.azure_base_url || !config.azure_model || !config.azure_api_version)) { 79 | console.log('Data source is Azure, but the corresponding configuration is missing. Please add it in the configuration file: ' + path.resolve(configFilePath)) 80 | process.exit(0); 81 | } 82 | 83 | function getConfig() { 84 | const content = fs.readFileSync(configFilePath).toString(); 85 | return JSON.parse(content || '{}'); 86 | } 87 | 88 | async function gptRequestAzure(prompt: string) { 89 | const res = await axios.post( 90 | `${config.azure_base_url}/openai/deployments/${config.azure_deployment_id}/chat/completions?api-version=${config.azure_api_version}`, 91 | { 92 | model: 'gpt-3.5-turbo-16k', 93 | messages: [{ role: 'user', content: prompt }], 94 | temperature: 0, 95 | top_p: 1, 96 | frequency_penalty: 0, 97 | presence_penalty: 0, 98 | }, 99 | { 100 | headers: { 101 | 'api-key': config.azure_api_key, 102 | }, 103 | timeout: 100000, 104 | } 105 | ); 106 | return res.data.choices[0].message.content; 107 | } 108 | 109 | async function gptRequestOpenai(prompt: string) { 110 | const openai_base_url = config.openai_base_url || 'https://api.openai.com'; 111 | const openai_model = config.openai_model || 'gpt-3.5-turbo-16k'; 112 | 113 | const res = await axios.post( 114 | `${openai_base_url}/v1/chat/completions`, 115 | { 116 | model: openai_model, 117 | messages: [{ role: 'user', content: prompt }], 118 | temperature: 0, 119 | top_p: 1, 120 | frequency_penalty: 0, 121 | presence_penalty: 0, 122 | }, 123 | { 124 | headers: { 125 | Authorization: `Bearer ${config.openai_api_key}`, 126 | }, 127 | timeout: 100000, 128 | } 129 | ); 130 | return res.data.choices[0].message.content; 131 | } 132 | 133 | export default async () => { 134 | // 执行 git diff,获取变更的文件内容 135 | const diff = child_process 136 | .execSync('git diff HEAD') 137 | .toString() 138 | .substring(0, 8000); 139 | 140 | const prompt = (config.language == 'zh' ? TEMPLACE_CN : TEMPLACE_EN).replace('{{diff}}', diff) 141 | const load = loading({ 142 | text: 'Generating commit log...', 143 | color: 'yellow', 144 | interval: 100, 145 | frames: ['◰', '◳', '◲', '◱'], 146 | }).start(); 147 | try { 148 | if (isVerbose) { 149 | console.log('--------- Input Prompt ----------'); 150 | console.log(prompt); 151 | } 152 | const res = config.datasource === 'azure' ? await gptRequestAzure(prompt) : await gptRequestOpenai(prompt); 153 | 154 | if (isVerbose) { 155 | console.log('\n--------- Output ----------'); 156 | console.log(res); 157 | console.log('-------------------'); 158 | } 159 | const commitLog = res.match(/([\s\S]*)<\/output>/)?.[1]?.trim(); 160 | if (!commitLog) { 161 | throw new Error('No commit log generated'); 162 | } 163 | load.stop(); 164 | load.succeed('Generate commit log success'); 165 | console.log('-------------------'); 166 | console.log(commitLog); 167 | 168 | const rl = readline.createInterface({ 169 | input: process.stdin, 170 | output: process.stdout, 171 | }); 172 | 173 | console.log('-------------------'); 174 | rl.question('Submit git commit with the log? (yes/no) ', (answer) => { 175 | if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') { 176 | child_process.execSync(`git commit -m "${commitLog}"`); 177 | load.succeed('commit success'); 178 | } else { 179 | } 180 | 181 | rl.close(); 182 | }); 183 | } catch (e) { 184 | load.stop(); 185 | load.fail('Generate commit log fail'); 186 | console.error(e.message); 187 | } 188 | }; 189 | -------------------------------------------------------------------------------- /src/template/template.en.md: -------------------------------------------------------------------------------- 1 | This is the git diff output: 2 | 3 | {{diff}} 4 | 5 | 6 | Role: You are a tool that generates git commit log based on git diff information. 7 | 8 | The following is the prefix of the git commit log and its corresponding usage scenario: 9 | * feat: new feature 10 | * fix: fix bug 11 | * docs: documentation 12 | * style: format (code changes that do not affect the running of the code) 13 | * refactor: refactoring (code changes that are neither new features nor bug fixes) 14 | * test: add test 15 | * chore: changes in the build process or auxiliary tools 16 | 17 | The final output format: 18 | 19 | [prefix]: brief description of the change 20 | 21 | * description of the modified content 22 | * description of the modified content 23 | ... 24 | 25 | 26 | Other requirements: 27 | * Start your commit log with the agreed prefix 28 | * Only one commit log is required 29 | * The description of the modified content does not exceed 3 30 | 31 | Please give the final commit log directly according to the above requirements, and wrap it in the and tags. -------------------------------------------------------------------------------- /src/template/template.zh.md: -------------------------------------------------------------------------------- 1 | 现有以下的 git diff 输出: 2 | 3 | {{diff}} 4 | 5 | 6 | 角色:你是一个根据 git diff 信息生成 git commit log 的工具,你会为一个复杂的变更生成一条精简的 commit log. 7 | 8 | 以下是 git commit log 的书写前缀及其对应的使用场景,请先挑选一个符合本次提交的前缀: 9 | * 新功能开发: feat 10 | * 修复 bug: fix 11 | * 文档修改: docs 12 | * 格式化代码: style 13 | * 重构代码: refactor 14 | * 增加测试: test 15 | * 更新版本号、配置文件等: chore 16 | 17 | 然后按照以下的格式输出最终的 commit log: 18 | 19 | [前缀]: 本次所有变更的简要描述 20 | 21 | * 每条修改的具体描述(不超过3条) 22 | 23 | 24 | 要求: 25 | * 使用中文输出 26 | * 如果有多种类型的修改,也强制压缩到一条 commit log 中,挑选出最重要的更改进行描述 27 | * 描述修改的功能,不描述修改了什么文件,也不描述修改了什么代码 28 | 29 | 请根据 git diff 的内容以及上述的要求,直接给出最终的 commit log,务必将最终的结果包裹在xml标签 中输出 -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0x95723d7a7a21E469F680c2634Bb929CcEae4ccDD' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /test/blah.test.ts: -------------------------------------------------------------------------------- 1 | import { sum } from '../src'; 2 | 3 | describe('blah', () => { 4 | it('works', () => { 5 | expect(sum(1, 1)).toEqual(2); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types", "index.d.ts"], 4 | "exclude": ["node_modules", "dist", "template"], 5 | "compilerOptions": { 6 | "module": "esnext", 7 | "lib": ["dom", "esnext"], 8 | "resolveJsonModule": true, 9 | "importHelpers": true, 10 | // output .d.ts declaration files for consumers 11 | "declaration": true, 12 | // output .js.map sourcemap files for consumers 13 | "sourceMap": true, 14 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 15 | "rootDir": "./src", 16 | // stricter type-checking for stronger correctness. Recommended by TS 17 | "strict": true, 18 | // linter checks for common issues 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true, 21 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | // use Node's module resolution algorithm, instead of the legacy TS one 25 | "moduleResolution": "node", 26 | // transpile JSX to React.createElement 27 | "jsx": "react", 28 | // interop between ESM and CJS modules. Recommended by TS 29 | "esModuleInterop": true, 30 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 31 | "skipLibCheck": true, 32 | // error out if import and file system have a casing mismatch. Recommended by TS 33 | "forceConsistentCasingInFileNames": true, 34 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 35 | "noEmit": true, 36 | } 37 | } 38 | --------------------------------------------------------------------------------