@lobehub/commit-cli
Version:
Lobe Commit is a CLI tool that uses ChatGPT to generate Gitmoji-based commit messages
33 lines (30 loc) • 37.3 kB
JavaScript
#!/usr/bin/env node
var Ge=Object.defineProperty;var m=(e,s)=>Ge(e,"name",{value:s,configurable:!0});import{jsx as n,jsxs as v,Fragment as ue}from"react/jsx-runtime";import{useTheme as x,Spinner as W,alert as N,Panel as _,SplitView as se,SelectInput as j,Badge as Ue,TextInput as I,MultiSelect as Le,Alert as P,ConfigPanel as Ve,render as R}from"@lobehub/cli-ui";import{Command as ze,Option as M}from"commander";import We from"update-notifier";import{Text as g,useInput as B,Box as pe}from"ink";import{memo as S,useState as y,useEffect as w,useRef as Be,useCallback as d,useMemo as q}from"react";import He from"dotenv";import Ke from"conf";import Ye from"swr";import qe from"chalk";import{minimatch as Xe}from"minimatch";import{execSync as Je}from"node:child_process";import X,{existsSync as Qe,readFileSync as Ze}from"node:fs";import he,{resolve as et}from"node:path";import tt from"openai";import{encode as st}from"gpt-tokenizer";import{kebabCase as fe,upperFirst as ot,debounce as G}from"lodash-es";import it from"pangu";import*as nt from"node:process";import ge from"node:process";import{execaSync as oe}from"execa";import{shallow as F}from"zustand/shallow";import{createWithEqualityFn as rt}from"zustand/traditional";import{Octokit as at}from"octokit";import ct from"gitconfig";import lt from"fast-deep-equal";var mt="@lobehub/commit-cli",ut="2.18.1",pt="Lobe Commit is a CLI tool that uses ChatGPT to generate Gitmoji-based commit messages",ht=["ai","git","commit","openai","gpt","gitmoji-cli","git-commits","chatgpt","aicommit","ai-commit"],ft="https://github.comlobehub/lobe-cli-toolbox/tree/master/packages/lobe-commit",gt={url:"https://github.com/lobehub/lobe-cli-toolbox/issues/new"},dt={type:"git",url:"https://github.com/lobehub/lobe-cli-toolbox.git"},bt="MIT",yt="LobeHub <i@lobehub.com>",kt=!1,St="module",vt={"@":"./src"},Tt={lobe:"dist/cli.js","lobe-commit":"dist/cli.js"},It=["dist"],Ct={build:"npm run type-check && pkgroll --minify -p tsconfig.prod.json --env.NODE_ENV=production && npm run shebang",dev:"pkgroll -p tsconfig.prod.json --env.NODE_ENV=development --watch",link:"npm run build && npm link -f",prepack:"clean-package",postpack:"clean-package restore",shebang:"lobe-shebang -t ./dist/cli.js",start:"node ./dist/cli.js",test:"vitest --passWithNoTests","test:coverage":"vitest run --coverage --passWithNoTests","type-check":"tsc --noEmit"},Et={"@lobehub/cli-ui":"1.12.0",chalk:"^5.4.1",commander:"^13.0.0",conf:"^13.1.0",dotenv:"^16.4.7",execa:"^9.5.2","fast-deep-equal":"^3.1.3",gitconfig:"^2.0.8","gpt-tokenizer":"^2.8.1",ink:"^6.0.0","lodash-es":"^4",minimatch:"^10.0.1",octokit:"^4.0.3",openai:"^4",pangu:"^4.0.7","path-exists":"^5.0.0",react:"^19.0.0",swr:"^2.3.0","update-notifier":"^7.3.1",zustand:"^5.0.3","zustand-utils":"^1.3.2"},wt="pnpm@10.10.0",_t={node:">=18"},Ot={access:"public",registry:"https://registry.npmjs.org/"},ie={name:mt,version:ut,description:pt,keywords:ht,homepage:ft,bugs:gt,repository:dt,license:bt,author:yt,sideEffects:kt,type:St,imports:vt,bin:Tt,files:It,scripts:Ct,dependencies:Et,packageManager:wt,engines:_t,publishConfig:Ot};const H=[{code:":sparkles:",desc:"Introduce new features",emoji:"\u2728",name:"sparkles",type:"feat"},{code:":bug:",desc:"Fix a bug",emoji:"\u{1F41B}",name:"bug",type:"fix"},{code:":recycle:",desc:"Refactor code that neither fixes a bug nor adds a feature",emoji:"\u267B\uFE0F",name:"recycle",type:"refactor"},{code:":zap:",desc:"A code change that improves performance",emoji:"\u26A1",name:"zap",type:"perf"},{code:":lipstick:",desc:"Add or update style files that do not affect the meaning of the code",emoji:"\u{1F484}",name:"lipstick",type:"style"},{code:":white_check_mark:",desc:"Adding missing tests or correcting existing tests",emoji:"\u2705",name:"white-check-mark",type:"test"},{code:":memo:",desc:"Documentation only changes",emoji:"\u{1F4DD}",name:"memo",type:"docs"},{code:":construction_worker:",desc:"Changes to our CI configuration files and scripts",emoji:"\u{1F477}",name:"construction-worker",type:"ci"},{code:":wrench:",desc:"Other changes that dont modify src or test file",emoji:"\u{1F527}",name:"wrench",type:"chore"},{code:":package:",desc:"Make architectural changes",emoji:"\u{1F4E6}",name:"package",type:"build"}],xt=m(e=>{const s=/^(\S+)\s/,t=/\s(\w+)(?=\(|:)/,o=/\(([^)]+)\)/,i=/:\s([^\n:]+)/s;return{body:e.indexOf(`
`)>0?e.slice(Math.max(0,e.indexOf(`
`)+1)).trim():void 0,emoji:s.test(e)&&e.match(s)?.[1]||"\u{1F527}",scope:o.test(e)?e.match(o)?.[1]:void 0,subject:i.test(e)&&e.match(i)?.[1]||"Nothing",type:t.test(e)&&e.match(t)?.[1]||"chore"}},"commitMessageToObj"),Nt=m(({emoji:e,type:s,scope:t,subject:o,issues:i,body:r,issuesType:l})=>{if(!s)return"waiting for selection...";const a=s.toLowerCase(),c=t&&fe(t).replaceAll(/\s+/g," "),u=ot(it.spacing(o).replaceAll(/\s+/g," ")),h=i&&i.replace("#","").replaceAll(/\s+/g," ").replaceAll(/[ ./|,]/g,",").split(",").filter(Boolean).map(f=>`${l?`${l} `:""}#${f}`);return`${e} ${a}${c?`(${c})`:""}: ${u}${h&&h?.length>0?` (${h.join(",")})`:""}${r?`
${r}`:""}`},"commotObjToMessage"),ne=m(e=>{const[s,...t]=e.split(": ");let o="\u{1F527}";for(const i of H)s.includes(i.type)&&(o=i.emoji);return`${o} ${s}: ${t.join(": ")}`},"addEmojiToMessage"),jt=/^https?:\/\/github\.com\/(.+?)\/(.+?)\.git$|^git@github\.com:(.+?)\/(.+?)\.git$/;var de=m(async()=>{try{const s=(await ct.get({location:"local"}))?.remote?.origin?.url;if(!s)return;const t=jt.exec(s),[o,i]=t.slice(3);return{owner:o,repoName:i}}catch{return}},"getRepo");const Pt=m((e,s)=>Promise.race([e,new Promise((t,o)=>{setTimeout(()=>o(new Error("TIMEOUT")),s)})]),"withTimeout");var Rt=m(async()=>{try{const e=await de();if(!e)return{error:"NO_REPO",success:!1};const s=k.getGithubToken();if(!s)return{error:"NO_TOKEN",success:!1};const t=new at({auth:s}),{data:o}=await Pt(t.rest.issues.listForRepo({owner:e.owner,repo:e.repoName,state:"open"}),5e3);return{data:o,success:!0}}catch(e){return e.message==="TIMEOUT"?{error:"TIMEOUT",success:!1}:e.status===401?{error:"INVALID_TOKEN",success:!1}:e.status===403?{error:"PERMISSION_DENIED",success:!1}:e.status===404?{error:"PERMISSION_DENIED",success:!1}:e.code==="ENOTFOUND"||e.code==="ECONNREFUSED"||e.code==="ETIMEDOUT"?{error:"NETWORK_ERROR",success:!1}:{error:"UNKNOWN_ERROR",success:!1}}},"getIssuesList");const Ft=m(e=>{switch(e){case"NO_TOKEN":return"No GitHub token provided";case"INVALID_TOKEN":return"Invalid GitHub token";case"PERMISSION_DENIED":return"Permission denied or private repository";case"TIMEOUT":return"Request timeout";case"NETWORK_ERROR":return"Network connection error";case"NO_REPO":return"Not a GitHub repository";default:return"Unknown error occurred"}},"getErrorMessage"),O=rt((e,s)=>({body:"",emoji:"",fetchIssuesList:m(async()=>{if(await de()){e({isGithubRepo:!0,issuesError:void 0,issuesLoading:!0});const o=await Rt();if(o.success&&o.data){const i=o.data.filter(r=>r.state==="open")||[];i.length>0?e({issueList:i,issuesLoading:!1,shouldSkipIssues:!1}):e({isGithubRepo:!1,issuesLoading:!1,shouldSkipIssues:!1})}else{const i=o.error==="NO_TOKEN"||o.error==="INVALID_TOKEN"||o.error==="PERMISSION_DENIED"||o.error==="TIMEOUT"||o.error==="NETWORK_ERROR";e({isGithubRepo:!1,issuesError:Ft(o.error),issuesLoading:!1,shouldSkipIssues:i})}}else e({shouldSkipIssues:!1})},"fetchIssuesList"),isGithubRepo:!1,issueList:[],issues:"",issuesError:void 0,issuesLoading:!1,issuesType:"",message:"",refreshMessage:m(()=>{const{issues:t,scope:o,subject:i,type:r,emoji:l,body:a,issuesType:c}=s(),u=Nt({body:a,emoji:l,issues:t,issuesType:c,scope:o,subject:i,type:r});e({message:u})},"refreshMessage"),scope:"",setEmoji:m(t=>{e({emoji:t}),s().refreshMessage()},"setEmoji"),setIssues:m(t=>{e({issues:t}),s().refreshMessage()},"setIssues"),setIssuesType:m(t=>{e({issuesType:t}),s().refreshMessage()},"setIssuesType"),setMessage:m(t=>{const o=xt(t);e({...o}),s().refreshMessage()},"setMessage"),setScope:m(t=>{e({scope:fe(t)}),s().refreshMessage()},"setScope"),setStep:m(t=>{e({step:t}),s().refreshMessage()},"setStep"),setSubject:m(t=>{e({subject:t}),s().refreshMessage()},"setSubject"),setType:m(t=>{e({type:t}),s().refreshMessage()},"setType"),shouldSkipIssues:!1,step:"type",subject:"",type:""}));var be=(e=>(e.CHATGPT_4O_LATEST="chatgpt-4o-latest",e.GPT_3_5_TURBO="gpt-3.5-turbo",e.GPT_3_5_TURBO_0125="gpt-3.5-turbo-0125",e.GPT_3_5_TURBO_1106="gpt-3.5-turbo-1106",e.GPT_4="gpt-4",e.GPT_4O="gpt-4o",e.GPT_4O_2024_05_13="gpt-4o-2024-05-13",e.GPT_4O_2024_11_20="gpt-4o-2024-11-20",e.GPT_4O_MINI="gpt-4o-mini",e.GPT_4_0125_PREVIEW="gpt-4-0125-preview",e.GPT_4_0613="gpt-4-0613",e.GPT_4_1="gpt-4.1",e.GPT_4_1106_PREVIEW="gpt-4-1106-preview",e.GPT_4_1_MINI="gpt-4.1-mini",e.GPT_4_1_NANO="gpt-4.1-nano",e.GPT_4_32K="gpt-4-32k",e.GPT_4_5_PREVIEW="gpt-4.5-preview",e.GPT_4_TURBO="gpt-4-turbo",e.GPT_4_TURBO_2024_04_09="gpt-4-turbo-2024-04-09",e.GPT_4_TURBO_PREVIEW="gpt-4-turbo-preview",e.O1="o1",e.O1_MINI="o1-mini",e.O1_PREVIEW="o1-preview",e.O3="o3",e.O3_MINI="o3-mini",e.O4_MINI="o4-mini",e))(be||{});const re={o3:2e5,"o4-mini":2e5,"gpt-4.1":1047576,"gpt-4.1-mini":1047576,"gpt-4.1-nano":1047576,"o3-mini":2e5,"o1-mini":128e3,o1:2e5,"o1-preview":128e3,"gpt-4.5-preview":128e3,"gpt-4o-mini":128e3,"gpt-4o-2024-11-20":128e3,"gpt-4o":128e3,"gpt-4o-2024-05-13":128e3,"chatgpt-4o-latest":128e3,"gpt-4-turbo":128e3,"gpt-4-turbo-2024-04-09":128e3,"gpt-4-turbo-preview":128e3,"gpt-4-0125-preview":128e3,"gpt-4-1106-preview":128e3,"gpt-4":8192,"gpt-4-0613":8192,"gpt-4-32k":32768,"gpt-3.5-turbo":16384,"gpt-3.5-turbo-0125":16384,"gpt-3.5-turbo-1106":16384},ye="o4-mini",ke={apiBaseUrl:{default:"",type:"string"},diffChunkSize:{default:re[ye]-512,type:"number"},emoji:{default:"emoji",type:"string"},githubToken:{default:"",type:"string"},includeWhy:{default:!1,type:"boolean"},locale:{default:"",type:"string"},maxLength:{default:100,type:"number"},messageTemplate:{default:"$msg",type:"string"},modelName:{default:ye,type:"string"},oneLineCommit:{default:!1,type:"boolean"},openaiToken:{default:"",type:"string"},prompt:{default:"",type:"string"},stream:{default:!0,type:"boolean"},useFullGitmoji:{default:!1,type:"boolean"}},ae=new Ke({projectName:"lobe-commit",schema:ke});He.config();const A=m(e=>ae.get(e),"getConfig"),At=m(e=>ke[e].default,"getDefulatConfig"),Dt=m((e,s)=>ae.set(e,s),"setConfig"),Se=m(()=>process.env.OPENAI_API_KEY||A("openaiToken"),"getOpenAIApiKey"),ve=m(()=>process.env.OPENAI_PROXY_URL||A("apiBaseUrl"),"getOpenAIProxyUrl"),Te=m(()=>process.env.GITHUB_TOKEN||process.env.GH_TOKEN||A("githubToken"),"getGithubToken"),$t=m(()=>re[A("modelName")],"getModelMaxToken"),Mt=m(()=>{let e=$t()-512;const s=A("diffChunkSize");return e>s?e:s},"getDiffChunkSize"),Gt=m(()=>({...ae.store,apiBaseUrl:ve(),diffChunkSize:Mt(),githubToken:Te(),openaiToken:Se(),stream:A("stream")}),"getCommitConfig");var k={getCommitConfig:Gt,getConfig:A,getDefulatConfig:At,getGithubToken:Te,getOpenAIApiKey:Se,getOpenAIProxyUrl:ve,setConfig:Dt};const Ut=m(()=>({get:k.getConfig,getDefault:k.getDefulatConfig,set:k.setConfig,store:k.getCommitConfig()}),"useConfStore"),Ie=S(()=>{const[e,s]=y(!0),t=x();return w(()=>{const o=setInterval(()=>{s(i=>!i)},500);return()=>clearInterval(o)},[]),n(g,{color:t.colorTextDescription,children:e?"\u258A":" "})}),Ce=S(({loading:e,loadingInfo:s,message:t,streamingMessage:o})=>k.getCommitConfig().stream?e?o?v(g,{children:[o,n(Ie,{})]}):n(g,{children:n(Ie,{})}):n(g,{children:t}):!e&&t?n(g,{children:t}):n(W,{label:s}));class me{static{m(this,"ChatPromptTemplate")}messages;constructor(s){this.messages=s}static fromMessages(s){const t=s.map(([o,i])=>({content:i,role:o}));return new me(t)}async formatMessages(s){return this.messages.map(t=>({content:this.formatString(t.content,s),role:t.role}))}formatString(s,t){let o=s;for(const[i,r]of Object.entries(t))if(r!==void 0){const l=new RegExp(`\\{${i}\\}`,"g");o=o.replace(l,r)}return o}}class ee{static{m(this,"PromptTemplate")}template;constructor(s){this.template=s}static fromTemplate(s){return new ee(s)}format(s){let t=this.template;for(const[o,i]of Object.entries(s))if(i!==void 0){const r=new RegExp(`\\{${o}\\}`,"g");t=t.replace(r,i)}return t}}const Lt=["You are an expert developer assistant specialized in generating high-quality Git commit messages.","Your mission is to create clean, comprehensive, and meaningful commit messages following the conventional commit convention.","","## Core Principles:","- Explain WHAT changes were made and WHY they were necessary",'- Use present tense, imperative mood (e.g., "Add feature" not "Added feature")',"- Be concise but descriptive","- Focus on the business impact and technical significance","","## Git Diff Analysis:","Analyze the provided git diff and identify:","1. The type of change (feature, fix, refactor, etc.)","2. The scope/area affected","3. The main purpose and impact","4. Any breaking changes or important details","","## Response Format:","Return ONLY the commit message without any explanations or metadata.","The message should be clear enough that another developer can understand the change without reading the diff."].join(`
`),Vt=["## Context Clues to Consider:","- File paths indicate the module/component affected","- Added/removed lines show the nature of changes","- Function/method names reveal the functionality involved","- Comments and documentation changes indicate intent","- Test file changes suggest the feature being tested"].join(`
`),Ee=H.map(e=>`- ${e.type}: ${e.desc}`).join(`
`),zt=k.getConfig("prompt")||Lt,J=k.getConfig("locale"),we=k.getConfig("maxLength"),Wt=k.getConfig("includeWhy")||!1,Bt=m(()=>me.fromMessages([["system",[zt,"",Vt,"","## Rules and Constraints:",`- Choose ONLY 1 type from the type-to-description below: <${Ee}>`,`- Commit message must be a maximum of ${we} characters`,"- Lines must not be longer than 74 characters","- Use clear, professional language",J&&`- Write the commit message in: ${J}`,Wt&&"- Include a brief explanation of WHY the change was made after the main message",'- Avoid redundant phrases like "This commit" or "This change"',"- Focus on user/business value when applicable"].filter(Boolean).join(`
`)],["user",["Please analyze the following git diff and generate a single, well-crafted commit message:","","{summary}","","Remember: Return ONLY the commit message, formatted according to conventional commits with the appropriate emoji prefix."].join(`
`)]]),"promptCommits"),_e=["You are an expert at analyzing code changes and creating meaningful summaries.","Your task is to analyze the git diff and extract the key information needed for generating a commit message.","","Focus on:","- What files were changed and their purpose","- What functionality was added, modified, or removed","- The technical approach used","- Any architectural or design decisions","","--------","{text}","--------","","Provide a clear, technical summary that highlights the most important changes and their context.","This summary will be used to generate a commit message, so focus on actionable information."].filter(Boolean).join(`
`),Ht=[_e,"","## Additional Guidelines:",`- Choose only 1 type from the type-to-description below: <${Ee}>`,"- Keep technical accuracy while being concise","- Highlight breaking changes or important behavioral modifications","- Consider the business impact of the changes",J&&`- Maintain consistency with ${J} language conventions`,`- Maximum output length: ${we} characters`].filter(Boolean).join(`
`),Kt=ee.fromTemplate(_e),Yt=ee.fromTemplate(Ht),K=m(e=>st(String(e)).length,"calcToken");class qt{static{m(this,"SummarizationChain")}client;questionPrompt;refinePrompt;constructor(s,t){this.client=s,this.questionPrompt=t.questionPrompt,this.refinePrompt=t.refinePrompt}async call(s){const{input_documents:t}=s;if(t.length===0)return{text:""};if(t.length===1){const i=t[0];if(!i)return{text:""};const r=this.questionPrompt.format({text:i.pageContent});return{text:(await this.client.chat.completions.create({messages:[{content:r,role:"user"}],model:"gpt-3.5-turbo",temperature:.5})).choices[0]?.message?.content||""}}let o="";for(const[i,r]of t.entries()){if(!r)continue;let l;i===0?l=this.questionPrompt.format({text:r.pageContent}):l=this.refinePrompt.format({existing_answer:o,text:r.pageContent}),o=(await this.client.chat.completions.create({messages:[{content:l,role:"user"}],model:"gpt-3.5-turbo",temperature:.5})).choices[0]?.message?.content||""}return{text:o}}}function Xt(e,s){return new qt(e,s)}m(Xt,"loadSummarizationChain");class Jt{static{m(this,"RecursiveCharacterTextSplitter")}chunkSize;chunkOverlap;lengthFunction;separators;constructor(s){this.chunkSize=s.chunkSize,this.chunkOverlap=s.chunkOverlap,this.lengthFunction=s.lengthFunction||(t=>t.length),this.separators=[`
`,`
`," ",""]}async createDocuments(s,t){const o=[];for(const[i,r]of s.entries()){const l=t?.[i]||{},a=this.splitText(r);for(const c of a)o.push({metadata:{...l},pageContent:c})}return o}splitText(s){const t=[];let o="",i=[];for(const l of this.separators)if(l===""?i=s.split(""):i=s.split(l),i.length>1){o=l;break}!o&&i.length<=1&&(o="",i=s.split(""));const r=[];for(const l of i)if(this.lengthFunction(l)<this.chunkSize)r.push(l);else{if(r.length>0){const c=this.mergeSplits(r,o);t.push(...c),r.length=0}const a=this.splitText(l);t.push(...a)}if(r.length>0){const l=this.mergeSplits(r,o);t.push(...l)}return t}mergeSplits(s,t){const o=[];let i=[],r=0;for(const a of s){const c=this.lengthFunction(a);if(r+c+(i.length>0?this.lengthFunction(t):0)>this.chunkSize&&i.length>0){const u=i.join(t);for(u.trim()&&o.push(u);(r>this.chunkOverlap||r+c+(i.length>0?this.lengthFunction(t):0)>this.chunkSize&&r>0)&&i.length!==0;){const h=i[0];if(!h)break;r-=this.lengthFunction(h)+(i.length>1?this.lengthFunction(t):0),i.shift()}}i.push(a),r+=c+(i.length>1?this.lengthFunction(t):0)}const l=i.join(t);return l.trim()&&o.push(l),o}}class Qt{static{m(this,"Commits")}client;config;textSplitter;prompt;constructor(){this.config=k.getCommitConfig(),this.config.openaiToken||N.error(`Please set the OpenAI Token by ${qe.bold.yellow("lobe-commit --config")}`,!0),this.client=new tt({apiKey:this.config.openaiToken,baseURL:this.config.apiBaseUrl,maxRetries:10}),this.textSplitter=new Jt({chunkOverlap:0,chunkSize:this.config.diffChunkSize,lengthFunction:m(s=>K(s),"lengthFunction")}),this.prompt=Bt()}async genCommit({setLoadingInfo:s,setSummary:t,cacheSummary:o,onStreamMessage:i,onTokenUsage:r}){s(" Generating...");const l=o||await this.genSummary({setLoadingInfo:s,setSummary:t}),a=await this.prompt.formatMessages({summary:l}),c=K(JSON.stringify(a));return this.config.stream&&i?this.genCommitStream(a,i,r,c):this.genCommitNonStream(a,r,c)}async genCommitStream(s,t,o,i){t("");const r=await this.client.chat.completions.create({messages:s,model:this.config.modelName,stream:!0,temperature:.5});let l="",a=i||0;for await(const c of r){const u=c.choices[0]?.delta?.content||"";if(u){l+=u;const h=ne(l.replace(/\((.*?)\):/,(f,b)=>f&&`(${b.toLowerCase()}):`));t(h),a+=K(u)}}return l||N.error("Diff summary failed, please check your network or try again...",!0),o?.(a),ne(l.replace(/\((.*?)\):/,(c,u)=>c&&`(${u.toLowerCase()}):`))}async genCommitNonStream(s,t,o){const r=(await this.client.chat.completions.create({messages:s,model:this.config.modelName,temperature:.5})).choices[0]?.message?.content;r||N.error("Diff summary failed, please check your network or try again...",!0);const l=K(r),a=(o||0)+l;return t?.(a),ne(r.replace(/\((.*?)\):/,(c,u)=>c&&`(${u.toLowerCase()}):`))}async checkDiffMaxToken(s){const t=await this.prompt.formatMessages({summary:s});return K(JSON.stringify(t))>re[this.config.modelName]-1024}async genSummary({setLoadingInfo:s,setSummary:t}){let o;const i=this.getDiff();if(await this.checkDiffMaxToken(i)){i||N.warn("No changes to commit",!0);const l=await this.textSplitter.createDocuments([i]);o=i,s(` [1/3] Split diff info to (${l.length}) by ${this.config.diffChunkSize} chunk size...`);const a=Xt(this.client,{questionPrompt:Kt,refinePrompt:Yt,type:"refine"});s(` [2/3] Split diff info to (${l.length} * ${this.config.diffChunkSize} chunk-size), generate summary...`);const c=await a.call({input_documents:l});c.text||N.error("Diff summary failed, please check your network or try again...",!0),o=String(c.text),s(" [3/3] Generate commit message..."),t(o)}else o=i;return o}getIgnorePatterns(){const s=et(process.cwd(),".lobecommitignore"),t=["*-lock.*","*.lock","**/*.jpg","**/*.jpeg","**/*.png","**/*.gif","**/*.svg","**/*.ico","**/*.webp","**/*.mp4","**/*.mp3","**/*.zip","**/*.tar","**/*.tar.gz","node_modules/**","dist/**","build/**","**/*.min.js","**/*.min.css"];if(Qe(s))try{const o=Ze(s,"utf8").split(`
`).map(i=>i.trim()).filter(i=>i&&!i.startsWith("#"));return[...t,...o]}catch(o){console.warn("Failed to read .lobecommitignore file:",o)}return t}filterDiffContent(s){const t=this.getIgnorePatterns(),o=s.split(`
`),i=[];let r=!1,l="";for(const a of o){if(a.startsWith("diff --git")){const c=a.match(/diff --git a\/(.+?) b\/(.+)/);if(c&&c[1]){l=c[1];const u=l;r=t.some(h=>Xe(u,h,{dot:!0}))}}r||i.push(a),a.startsWith("diff --git")&&i.length>0&&(r=!1)}return i.join(`
`)}compressDiff(s,t=200){const o=s.split(`
`);if(o.length<=t)return s;const i=[],r=[],l=[];for(const c of o)c.startsWith("diff --git")||c.startsWith("index ")||c.startsWith("+++")||c.startsWith("---")?i.push(c):c.startsWith("+")||c.startsWith("-")?l.push(c):(c.startsWith("@@")||(c.trim()===""||c.startsWith(" "))&&i.length+l.length+r.length<t*.8)&&r.push(c);const a=[...i,...l.slice(0,Math.max(50,t-i.length-r.length)),...r.slice(0,Math.max(20,t-i.length-l.length))];return a.length<o.length&&a.push(`
... (${o.length-a.length} lines truncated for brevity) ...`),a.join(`
`)}getDiff(){const s=Je("git diff --staged --ignore-all-space --diff-algorithm=minimal --function-context --no-ext-diff --no-color",{maxBuffer:1152921504606847e3}).toString(),t=this.filterDiffContent(s);return this.compressDiff(t)}}const Oe=m(({setMessage:e,onSuccess:s,onError:t,...o}={})=>{const i=Be(new Qt),[r,l]=y(""),[a,c]=y(" Generating..."),[u,h]=y(!1),[f,b]=y(!0),[$,C]=y(Date.now().toString()),[L,V]=y(""),[z,p]=y(0),T=k.getCommitConfig(),Pe=d(E=>{V(E),e?.(E),b(!1)},[e]),Re=d(E=>{p(E)},[]),{data:Fe,isLoading:Ae}=Ye(u?$:null,async()=>(T.stream&&b(!1),i.current.genCommit({cacheSummary:r,onStreamMessage:T.stream?Pe:void 0,onTokenUsage:Re,setLoadingInfo:c,setSummary:l})),{onError:m((E,...te)=>{t?.(E,...te),N.error(E,!0)},"onError"),onErrorRetry:m(()=>!1,"onErrorRetry"),onSuccess:m((E,...te)=>{h(!1),E&&!T.stream&&e?.(E),s?.(E,...te),b(!1)},"onSuccess"),...o}),De=d(()=>{C(Date.now().toString()),b(!0),V(""),p(0),h(!0)},[]),$e=d(()=>{l(""),C(Date.now().toString()),b(!0),V(""),p(0),h(!0)},[]),Me=d(()=>{b(!1),h(!1)},[]);return{loading:Ae||f,loadingInfo:a,message:T.stream?L:Fe,restart:$e,start:De,stop:Me,summary:r,tokenUsage:z}},"useCommits"),Zt=S(()=>{const[e,s]=y(""),t=x(),o=k.getCommitConfig(),{summary:i,start:r,loadingInfo:l,loading:a,tokenUsage:c,message:u}=Oe({setMessage:s});return w(()=>{r()},[r]),n(_,{footer:i&&v(g,{color:t.colorTextDescription,children:[n(g,{bold:!0,children:"\u{1F449} DIFF SUMMARY: "}),i]}),reverse:!0,title:`\u{1F92F} AI Commit Generator ${o.stream?"(Streaming)":""} ${c>0?`[Tokens: ${c}]`:""}`,children:n(Ce,{loading:a,loadingInfo:l,message:e,streamingMessage:u})})}),Y={CONTENTS:`#!/usr/bin/env sh
# lobe-commit as a commit hook
if npx -v >/dev/null 2>&1
then
exec < /dev/tty
npx -c "lobe-commit --hook $1"
else
exec < /dev/tty
lobe-commit --hook $1
fi`,FILENAME:"prepare-commit-msg",PERMISSIONS:509},Q="not git";var ce=m(e=>{try{try{const{stdout:s}=oe("git",["config","--get","core.hooksPath"]);return he.resolve(s,e)}catch{const{stdout:s}=oe("git",["rev-parse","--absolute-git-dir"]);return he.resolve(s+"/hooks",e)}}catch{return N.error("Please check if this is a git folder",!0),Q}},"getAbsoluteHooksPath");const es=S(()=>{const{message:e,setMessage:s,setStep:t}=O(C=>({message:C.message,setMessage:C.setMessage,setStep:C.setStep}));B(d((C,L)=>L.tab&&t("type"),[]));const o=x(),i=k.getCommitConfig(),{summary:r,start:l,loadingInfo:a,loading:c,restart:u,tokenUsage:h,message:f}=Oe({setMessage:s}),b=d(C=>{switch(C.value){case"reloadFromSummary":{l();break}case"reload":{u();break}case"edit":{t("type");break}case"confirm":{t("commit");break}}},[l,u]);w(()=>{l()},[l]);const $=q(()=>[r&&{label:"\u{1F504}\uFE0F Regenerate commit message from summary [FAST]",value:"reloadFromSummary"},{label:"\u{1F504}\uFE0F Regenerate full commit message [SLOW]",value:"reload"},{label:"\u270F\uFE0F Edit this message",value:"edit"},{label:"\u2705 Use this message",value:"confirm"}].filter(Boolean),[r]);return v(_,{footer:!c&&e&&n(j,{items:$,onSelect:b}),title:`\u{1F92F} AI Commit Generator ${i.stream?"(Streaming)":""} ${h>0?`[Tokens: ${h}]`:""}`,children:[r&&n(se,{direction:"bottom",children:v(g,{color:o.colorTextDescription,children:[n(g,{bold:!0,children:"\u{1F449} DIFF SUMMARY: "}),r]})}),n(Ce,{loading:c,loadingInfo:a,message:e,streamingMessage:f})]})}),U=S(({step:e,steps:s,title:t})=>{const o=x();return v(g,{children:[n(Ue,{color:o.colorText,children:`${e}/${s}`}),n(g,{bold:!0,children:` ${t.toUpperCase()}`})]})}),ts=S(()=>{const{message:e,setIssues:s,setStep:t,issues:o,fetchIssuesList:i,isGithubRepo:r,issuesLoading:l,issuesError:a,shouldSkipIssues:c}=O(p=>({fetchIssuesList:p.fetchIssuesList,isGithubRepo:p.isGithubRepo,issues:p.issues,issuesError:p.issuesError,issuesLoading:p.issuesLoading,message:p.message,refreshMessage:p.refreshMessage,setIssues:p.setIssues,setStep:p.setStep,shouldSkipIssues:p.shouldSkipIssues}),F),u=O(p=>p.issueList,lt);B(d((p,T)=>T.tab&&t("subject"),[]));const[h,f]=y(""),b=x();w(()=>{i()},[]),w(()=>{if(c&&a&&!l){const p=setTimeout(()=>{t("commit")},2e3);return()=>clearTimeout(p)}},[c,a,l,t]);const $=q(()=>{let p=u;return h&&(p=p.filter(T=>T.title.toLowerCase().includes(h)||String(T.number).includes(h))),p.map(T=>({label:v(ue,{children:[n(g,{backgroundColor:b.colorBgLayout,color:b.colorText,children:` #${T.number} `}),` ${T.title}`]}),value:String(T.number)}))},[h,u]),C=d(p=>{f(p.replaceAll(" ",""))},[]),L=d(p=>{s(p.join(",")),f("")},[]),V=d(()=>n(I,{defaultValue:h,onChange:G(C,100),placeholder:"Input to keywords to filter issues, press [Space] to multi-select..."}),[o]),z=d(()=>{t(o?"issuesType":"commit")},[o]);return a?v(_,{footer:n(g,{children:e}),header:n(U,{step:4,steps:4,title:"Link issues (optional)"}),children:[v(g,{color:b.colorError,children:["\u26A0\uFE0F ",a]}),n(g,{color:b.colorWarning,children:c?"Automatically skipping issues step in 2 seconds, or press [Enter] to skip now...":"Press [Enter] to skip linking issues and continue..."}),n(I,{onChange:G(s,100),onSubmit:z,placeholder:"Press [Enter] to skip or input issue numbers manually..."})]}):n(_,{footer:n(g,{children:e}),header:n(U,{step:4,steps:4,title:"Link issues (optional)"}),children:r?l?n(W,{label:" Loading issues..."}):v(ue,{children:[n(V,{}),v(se,{children:[n(Le,{defaultValue:o.split(","),onChange:L,onSubmit:z,options:$}),$.length===0&&n(g,{color:b.colorWarning,children:"No issues found, press [Enter] to skip..."})]})]}):n(I,{defaultValue:o,onChange:G(s,100),onSubmit:z,placeholder:"Input number to link issues, press [Enter] to confirm or skip..."})})}),ss=[{label:"Only link issues",value:""},{label:"close #X",value:"close"},{label:"fix #X",value:"fix"},{label:"resolve #X",value:"resolve"}],os=S(()=>{const{message:e,setIssuesType:s,setStep:t}=O(o=>({message:o.message,setIssuesType:o.setIssuesType,setStep:o.setStep}),F);return B(d((o,i)=>i.tab&&t("issues"),[])),n(_,{footer:n(g,{children:e}),header:n(U,{step:4,steps:4,title:"Link issues (optional)"}),children:n(j,{items:ss,onHighlight:m(o=>s(o.value),"onHighlight"),onSelect:m(()=>t("commit"),"onSelect")})})}),Z=S(({item:e})=>{const s=x();return v(pe,{children:[n(pe,{marginRight:1,width:20,children:n(g,{backgroundColor:s.colorBgLayout,color:s.colorText,children:` ${[e.emoji,e.type].filter(Boolean).join(" ")} `})}),n(g,{color:s.colorTextDescription,children:`- ${e.desc}`})]},e.name)}),is=S(()=>n(_,{title:"\u{1F92F} Gitmoji list",children:H.map(e=>n(Z,{item:e},e.name))})),xe="Use Input Value",ns=[{label:"Input commit scope",value:xe},{label:"Package management changes, such as adding, updating, or removing dependencies",value:"deps"},{label:"Configuration file changes, such as adding, updating, or removing configuration options",value:"config"},{label:"User interface changes, such as layout, style, or interaction modifications",value:"ui"},{label:"API interface changes, such as adding, modifying, or removing API endpoints",value:"api"},{label:"Database changes, such as adding, modifying, or removing tables, fields, or indexes",value:"database"},{label:"Data model changes, such as adding, modifying, or removing data models",value:"model"},{label:"Controller changes, such as adding, modifying, or removing controllers",value:"controller"},{label:"View changes, such as adding, modifying, or removing views",value:"view"},{label:"Route changes, such as adding, modifying, or removing routes",value:"route"},{label:"Test changes, such as adding, modifying, or removing test cases",value:"test"}],rs=ns.map(e=>({label:n(Z,{item:{desc:e.label,name:e.value,type:e.value}}),value:e.value})),as=S(()=>{const{message:e,setScope:s,setStep:t,scope:o}=O(f=>({message:f.message,scope:f.scope,setScope:f.setScope,setStep:f.setStep}),F);B(d((f,b)=>b.tab&&t("type"),[]));const[i,r]=y(!0),[l,a]=y(""),c=d(()=>{t("subject")},[]),u=d(f=>{i&&(s(f),a(f))},[i]),h=d(f=>{f.value===xe?(r(!0),s(l)):(r(!1),s(f.value))},[]);return v(_,{footer:n(g,{children:e}),header:n(U,{step:2,steps:4,title:"Input commit scope (optional)"}),children:[n(I,{defaultValue:o,onChange:G(u,100),onSubmit:c,placeholder:"Input commit <scope>, or select below, press [Enter] to skip..."}),n(se,{children:n(j,{itemComponent:m(({label:f})=>f,"itemComponent"),items:rs,onHighlight:h,onSelect:c})})]})}),cs=S(()=>{const{message:e,setSubject:s,setStep:t,subject:o,shouldSkipIssues:i,fetchIssuesList:r}=O(a=>({fetchIssuesList:a.fetchIssuesList,message:a.message,setStep:a.setStep,setSubject:a.setSubject,shouldSkipIssues:a.shouldSkipIssues,subject:a.subject}),F);B(d((a,c)=>c.tab&&t("scope"),[])),w(()=>{r()},[r]);const l=d(()=>{o&&t(i?"commit":"issues")},[o,i,t]);return n(_,{footer:n(g,{children:e}),header:n(U,{step:3,steps:4,title:"Input commit subject"}),children:n(I,{defaultValue:o,onChange:G(s,100),onSubmit:l,placeholder:"Input commit <subject>..."})})}),ls=k.getConfig("emoji")==="emoji",ms="ai",us={label:n(Z,{item:{desc:"generate commit message by ChatGPT",emoji:"\u{1F92F}",name:"ai",type:"Use AI Commit"}}),value:"ai"},ps=S(()=>{const{setType:e,setStep:s,setEmoji:t,type:o}=O(c=>({setEmoji:c.setEmoji,setStep:c.setStep,setType:c.setType,type:c.type}),F),[i,r]=y(o),l=q(()=>{let c=H;return i?c=c.filter(u=>u.type.includes(i)):o&&(c=c.filter(u=>u.type.includes(o))),c.map(u=>({label:n(Z,{item:u}),value:`${ls?u.emoji:u.code} ${u.type}`}))},[i,o]),a=d(c=>{if(c.value===ms)s("ai");else{const u=c.value.split(" ");t(u[0]),e(u[1]),s("scope")}},[]);return n(_,{footer:n(I,{defaultValue:o,onChange:G(r,100),placeholder:"Search commit <type>..."}),header:n(U,{step:1,steps:4,title:"Select commit type"}),reverse:!0,children:n(j,{itemComponent:m(({label:c})=>c,"itemComponent"),items:[...l,us],onSelect:a})})}),hs=S(({hook:e})=>{const{message:s}=O(i=>({message:i.message}),F),[t,o]=y(!0);try{return w(()=>{if(e){const i=typeof e=="string"?e:process.argv[3];i&&X.writeFileSync(i,s),o(!1)}else oe("git",["commit","-m",s],{buffer:!1,stdio:"inherit"}),o(!1)},[]),t?n(W,{label:" Committing..."}):n(P,{variant:"success",children:" Successfully committed!"})}catch(i){return n(P,{variant:"error",children:` ${i.message}`})}}),Ne=S(({hook:e})=>{const[s,t]=y(!1),{step:o}=O(i=>({step:i.step}),F);if(w(()=>{const i=ce(Y.FILENAME);i===Q&&nt.exit(1),X.existsSync(i)&&t(!0)},[]),!e&&s)return n(P,{variant:"warning",children:'Lobe Commit is in hook mode, use "git commit" instead.'});if(o==="type")return n(ps,{});if(o==="scope")return n(as,{});if(o==="subject")return n(cs,{});if(o==="issues")return n(ts,{});if(o==="issuesType")return n(os,{});if(o==="ai")return n(es,{});if(o==="commit")return n(hs,{hook:e})});H.map(e=>`- ${e.type}: ${e.desc}`).join(`
`),k.getConfig("prompt"),k.getConfig("locale"),k.getConfig("maxLength");const le=`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 enter a git diff summary, and your job is to convert it into a useful commit message.Add a short description of the changes are done after the commit message. Don't start it with "This commit", just describe the changes.Use the present tense. Lines must not be longer than 74 characters.`,fs=S(()=>{const[e,s]=y(),{store:t,set:o,getDefault:i}=Ut(),r=m((a,c)=>{o(a,c),s("")},"setConfig"),l=q(()=>[{children:n(j,{items:[{label:"\u{1F604}",value:"emoji"},{label:":smile:",value:"code"}],onSelect:m(a=>r("emoji",a.value),"onSelect")}),defaultValue:i("emoji"),key:"emoji",label:"Emoji format",value:t.emoji},{children:n(I,{defaultValue:t.locale,onSubmit:m(a=>r("locale",a),"onSubmit"),placeholder:"Input commit message locale..."}),defaultValue:i("locale")||"en_US",desc:"Commit message locale, default as en_US",key:"local",label:"AI message locale",value:t.locale||"en_US"},{children:n(I,{defaultValue:t.prompt,onSubmit:m(a=>r("prompt",a),"onSubmit"),placeholder:"Input ChatGPT prompt..."}),defaultValue:i("prompt")||le,desc:le,key:"prompt",label:"Custom prompt",showValue:!1,value:t.prompt||le},{children:n(j,{items:Object.values(be).map(a=>({label:a,value:a})),onSelect:m(a=>r("modelName",a.value),"onSelect")}),defaultValue:i("modelName"),desc:`Default model as ${i("modelName")}`,key:"modelName",label:"Model Name",value:t.modelName},{children:n(I,{defaultValue:String(t.diffChunkSize),onSubmit:m(a=>r("diffChunkSize",Number(a)),"onSubmit"),placeholder:"Input diff split chunk size ..."}),defaultValue:i("diffChunkSize"),desc:`Default chunk size as ${i("diffChunkSize")}`,key:"diffChunkSize",label:"Diff split chunk size",value:t.diffChunkSize},{children:n(I,{defaultValue:String(t.maxLength),onSubmit:m(a=>r("maxLength",Number(a)),"onSubmit"),placeholder:"Input maximum character length of the generated commit message..."}),defaultValue:i("maxLength"),desc:`The maximum character length of the generated commit message, default max-length as ${i("maxLength")}`,key:"maxLength",label:"Commit message max-length",value:t.maxLength},{children:n(j,{items:[{label:"Enabled",value:"true"},{label:"Disabled",value:"false"}],onSelect:m(a=>r("stream",a.value==="true"),"onSelect")}),defaultValue:i("stream"),desc:"Enable streaming output for AI responses, default as enabled",key:"stream",label:"Streaming output",showValue:!1,value:t.stream},{children:n(I,{defaultValue:t.openaiToken,onSubmit:m(a=>r("openaiToken",a),"onSubmit"),placeholder:"Input OpenAI token..."}),defaultValue:i("openaiToken"),key:"openaiToken",label:"OpenAI token",showValue:!1,value:t.openaiToken},{children:n(I,{defaultValue:t.apiBaseUrl,onSubmit:m(a=>r("apiBaseUrl",a),"onSubmit"),placeholder:"Set openAI API proxy, default value: https://api.openai.com/v1/..."}),defaultValue:i("apiBaseUrl"),desc:"OpenAI API proxy, default value: https://api.openai.com/v1/",key:"apiBaseUrl",label:"OpenAI API proxy",showValue:!1,value:t.apiBaseUrl},{children:n(I,{defaultValue:t.githubToken,onSubmit:m(a=>r("githubToken",a),"onSubmit"),placeholder:"Input Github token..."}),defaultValue:i("githubToken"),key:"githubToken",label:"Github token",showValue:!1,value:t.githubToken}],[t]);return n(Ve,{active:e,items:l,logo:"\u{1F92F}",setActive:s,title:"Lobe Commit Config"})}),gs=S(()=>{const[e,s]=y(!0),t=x();try{return w(()=>{const o=ce(Y.FILENAME);o===Q&&ge.exit(1),X.writeFileSync(o,Y.CONTENTS,{mode:Y.PERMISSIONS}),s(!1)},[]),e?n(W,{label:" Loading..."}):v(P,{variant:"success",children:[" lobe-commit hook ",n(g,{color:t.colorSuccess,children:"created"})," successfully!"]})}catch{return n(P,{variant:"error",children:" lobe-commit commit hook is not created"})}}),ds=S(()=>{const[e,s]=y(!0),t=x();try{return w(()=>{const o=ce(Y.FILENAME);o===Q&&ge.exit(1),X.unlinkSync(o),s(!1)},[]),e?n(W,{label:" Loading..."}):v(P,{variant:"success",children:[" lobe-commit hook ",n(g,{color:t.colorError,children:"removed"})," successfully!"]})}catch{return n(P,{variant:"error",children:" lobe-commit commit hook is not found"})}}),bs=We({pkg:ie,shouldNotifyInNpmScript:!0});bs.notify({isGlobal:!0});const je=new ze;je.name("lobe-commit").description(ie.description).version(ie.version).addOption(new M("--hook <file>","Interactively commit using the prompts")).addOption(new M("-a, --ai","Generate prompts by ChatGPT")).addOption(new M("-o, --option","Setup lobe-commit preferences")).addOption(new M("-i, --init","Initialize lobe-commit as a commit hook")).addOption(new M("-r, --remove","Remove a previously initialized commit hook")).addOption(new M("-l, --list","List all commit types supported")).parse();const D=je.opts();D.ai?R(n(Zt,{})):D.option?R(n(fs,{})):D.init?R(n(gs,{})):D.remove?R(n(ds,{})):D.list?R(n(is,{})):D.hook?R(n(Ne,{hook:D.hook})):R(n(Ne,{}));