UNPKG

lazycommitt

Version:

Writes your git commit messages for you with AI using Groq

152 lines (127 loc) 23.3 kB
#!/usr/bin/env node var ve=Object.defineProperty;var c=(e,t)=>ve(e,"name",{value:t,configurable:!0});import{command as K,cli as $e}from"cleye";import{readFileSync as Ae}from"fs";import{fileURLToPath as B,pathToFileURL as xe}from"url";import j,{dirname as Ce,join as Ee}from"path";import{execa as x}from"execa";import{dim as Y,bgCyan as J,black as X,green as I,red as P}from"kolorist";import{intro as q,spinner as G,select as V,isCancel as k,outro as A,text as Z,confirm as ee}from"@clack/prompts";import b from"fs/promises";import ke from"os";import te from"ini";import se from"groq-sdk";var Oe="1.0.17",Ie={version:Oe};const H=class H extends Error{};c(H,"KnownError");let y=H;const F=" ",S=c(e=>{e instanceof Error&&!(e instanceof y)&&(e.stack&&console.error(Y(e.stack.split(` `).slice(1).join(` `))),console.error(` ${F}${Y(`lazycommit v${Ie.version}`)}`),console.error(` ${F}Please open a Bug report with the information above:`),console.error(`${F}https://github.com/KartikLabhshetwar/lazycommit/issues/new/choose`))},"handleCliError"),ne=c(async()=>{const{stdout:e,failed:t}=await x("git",["rev-parse","--show-toplevel"],{reject:!1});if(t)throw new y("The current directory must be a Git repository!");return e},"assertGitRepo"),D=c(e=>`:(exclude)${e}`,"excludeFromDiff"),U=["package-lock.json","node_modules/**","dist/**","build/**",".next/**","coverage/**",".nyc_output/**","*.log","*.tmp","*.temp","*.cache",".DS_Store","Thumbs.db","*.min.js","*.min.css","*.bundle.js","*.bundle.css","*.lock"].map(D),ae=c(async e=>{const t=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await x("git",[...t,"--name-only",...U,...e?e.map(D):[]]);if(!s)return;const{stdout:a}=await x("git",[...t,...U,...e?e.map(D):[]]);return{files:s.split(` `),diff:a}},"getStagedDiff"),oe=c(e=>`Detected ${e.length.toLocaleString()} staged file${e.length>1?"s":""}`,"getDetectedMessage"),ue=c(async e=>{const t=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await x("git",[...t,"--name-only",...U,...e?e.map(D):[]]);if(!s)return null;const a=s.split(` `).filter(Boolean),n=await Promise.all(a.map(async o=>{try{const{stdout:u}=await x("git",[...t,"--numstat","--",o]),[r,d]=u.split(" ").slice(0,2).map(Number);return{file:o,additions:r||0,deletions:d||0,changes:(r||0)+(d||0)}}catch{return{file:o,additions:0,deletions:0,changes:0}}}));return{files:a,fileStats:n,totalChanges:n.reduce((o,u)=>o+u.changes,0)}},"getDiffSummary"),ie=c(async(e,t=20)=>{const s=await ue(e);if(!s)return null;const{fileStats:a}=s,n=[...a].sort((i,f)=>f.changes-i.changes),o=n.slice(0,Math.max(1,t)),u=s.files.length,r=s.totalChanges,d=a.reduce((i,f)=>i+(f.additions||0),0),h=a.reduce((i,f)=>i+(f.deletions||0),0),p=[];p.push(`Files changed: ${u}`),p.push(`Additions: ${d}, Deletions: ${h}, Total changes: ${r}`),p.push("Top files by changes:");for(const i of o)p.push(`- ${i.file} (+${i.additions} / -${i.deletions}, ${i.changes} changes)`);return n.length>o.length&&p.push(`\u2026and ${n.length-o.length} more files`),p.join(` `)},"buildCompactSummary"),re=c(e=>b.lstat(e).then(()=>!0,()=>!1),"fileExists"),Pe=["","conventional"],{hasOwnProperty:Se}=Object.prototype,ce=c((e,t)=>Se.call(e,t),"hasOwn"),v=c((e,t,s)=>{if(!t)throw new y(`Invalid config property ${e}: ${s}`)},"parseAssert"),M={GROQ_API_KEY(e){if(!e)throw new y("Please set your Groq API key via `lazycommit config set GROQ_API_KEY=<your token>`");return v("GROQ_API_KEY",e.startsWith("gsk_"),'Must start with "gsk_"'),e},locale(e){return e?(v("locale",e,"Cannot be empty"),v("locale",/^[a-z-]+$/i.test(e),"Must be a valid locale (letters and dashes/underscores). You can consult the list of codes in: https://wikipedia.org/wiki/List_of_ISO_639-1_codes"),e):"en"},generate(e){if(!e)return 1;v("generate",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return v("generate",t>0,"Must be greater than 0"),v("generate",t<=5,"Must be less or equal to 5"),t},type(e){return e?(v("type",Pe.includes(e),"Invalid commit type"),e):""},proxy(e){if(!(!e||e.length===0))return v("proxy",/^https?:\/\//.test(e),"Must be a valid URL"),e},model(e){return!e||e.length===0?"openai/gpt-oss-20b":e},timeout(e){if(!e)return 1e4;v("timeout",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return v("timeout",t>=500,"Must be greater than 500ms"),t},"max-length"(e){if(!e)return 100;v("max-length",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return v("max-length",t>=20,"Must be greater than 20 characters"),v("max-length",t<=200,"Must be less than or equal to 200 characters"),t}},W=j.join(ke.homedir(),".lazycommit"),le=c(async()=>{if(!await re(W))return Object.create(null);const t=await b.readFile(W,"utf8");return te.parse(t)},"readConfigFile"),L=c(async(e,t)=>{const s=await le(),a={};for(const n of Object.keys(M)){const o=M[n],u=e?.[n]??s[n];if(t)try{a[n]=o(u)}catch{}else a[n]=o(u)}return a},"getConfig"),De=c(async e=>{const t=await le();for(const[s,a]of e){if(!ce(M,s))throw new y(`Invalid config property: ${s}`);const n=M[s](a);t[s]=n}await b.writeFile(W,te.stringify(t),"utf8")},"setConfigs"),Me={"":"<commit message>",conventional:"<type>(<optional scope>): <commit message>"},me={"":"",conventional:`Choose the most appropriate type from the following categories that best describes the git diff: ${JSON.stringify({feat:"A NEW user-facing feature or functionality that adds capabilities",fix:"A bug fix that resolves an existing issue",docs:"Documentation only changes (README, comments, etc)",style:"Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)",refactor:"Code restructuring, improvements, or internal changes that enhance existing functionality",perf:"A code change that improves performance",test:"Adding missing tests or correcting existing tests",build:"Changes that affect the build system or external dependencies",ci:"Changes to our CI configuration files and scripts",chore:"Maintenance tasks, config updates, dependency updates, or internal tooling changes",revert:"Reverts a previous commit"},null,2)} IMPORTANT: - Use 'feat' ONLY for NEW user-facing features - Use 'refactor' for code improvements, restructuring, or internal changes - Use 'chore' for config updates, maintenance, or internal tooling - Use the exact type name from the list above.`},fe=c((e,t,s)=>`You are a professional git commit message generator. Generate ONLY conventional commit messages. CRITICAL RULES: - Return ONLY the commit message line, nothing else - Use format: type: subject (NO scope, just type and subject) - Maximum ${t} characters (be concise but complete) - Imperative mood, present tense - Be specific and descriptive - NO explanations, questions, or meta-commentary - ALWAYS complete the message - never truncate mid-sentence COMMIT TYPES: - feat: NEW user-facing feature or functionality - fix: bug fix that resolves an issue - docs: documentation changes only - style: formatting, no logic change - refactor: code restructuring, improvements, or internal changes - perf: performance improvements - test: adding/updating tests - build: build system changes - ci: CI/CD changes - chore: maintenance tasks, dependencies, config updates QUALITY GUIDELINES: - Start with the most important change - Use specific, descriptive language - Include the main component/area affected - Be clear about what was done, not just what files changed - Use proper grammar and punctuation EXAMPLES (correct format - NO scope, just type and subject): - feat: add user login with OAuth integration - fix: resolve memory leak in image processing service - refactor: improve message generation with better prompts - refactor: increase default max-length from 50 to 100 - docs: update installation and configuration guide - test: add unit tests for JWT token validation - chore: update axios to v1.6.0 for security patches WRONG FORMAT (do not use): - feat(auth): add user login - refactor(commit): improve prompts ${me[s]?` DETAILED TYPE GUIDELINES: ${me[s]}`:""} Language: ${e} Output format: ${Me[s]||"type: subject"} Generate a single, complete, professional commit message that accurately describes the changes.`,"generatePrompt"),Te=c(async(e,t,s,a,n,o,u,r,d,h,p)=>{const i=new se({apiKey:e,timeout:h});try{return d>1?{choices:(await Promise.all(Array.from({length:d},()=>i.chat.completions.create({model:t,messages:s,temperature:a,top_p:n,frequency_penalty:o,presence_penalty:u,max_tokens:r,n:1})))).flatMap(m=>m.choices)}:await i.chat.completions.create({model:t,messages:s,temperature:a,top_p:n,frequency_penalty:o,presence_penalty:u,max_tokens:r,n:1})}catch(f){if(f instanceof se.APIError){let l=`Groq API Error: ${f.status} - ${f.name}`;throw f.message&&(l+=` ${f.message}`),f.status===500&&(l+=` Check the API status: https://console.groq.com/status`),(f.status===413||f.message&&f.message.includes("rate_limit_exceeded"))&&(l+=` \u{1F4A1} Tip: Your diff is too large. Try: 1. Commit files in smaller batches 2. Exclude large files with --exclude 3. Use a different model with --model 4. Check if you have build artifacts staged (dist/, .next/, etc.)`),new y(l)}throw f.code==="ENOTFOUND"?new y(`Error connecting to ${f.hostname} (${f.syscall}). Are you connected to the internet?`):f}},"createChatCompletion"),ge=c(e=>e.trim().replace(/^["']|["']\.?$/g,"").replace(/[\n\r]/g,"").replace(/(\w)\.$/,"$1"),"sanitizeMessage"),de=c((e,t)=>{if(e.length<=t)return e;const s=e.slice(0,t),a=Math.max(s.lastIndexOf(". "),s.lastIndexOf("! "),s.lastIndexOf("? "));if(a>t*.7)return s.slice(0,a+1);const n=Math.max(s.lastIndexOf(", "),s.lastIndexOf("; "));if(n>t*.6)return s.slice(0,n+1);const o=s.lastIndexOf(" ");return o>t*.5?s.slice(0,o):e.length>t+10?s+"...":s},"enforceMaxLength"),_e=c(e=>Array.from(new Set(e)),"deduplicateMessages"),Re=["feat:","fix:","docs:","style:","refactor:","perf:","test:","build:","ci:","chore:","revert:"],Ne=c((e,t)=>{const s=e.replace(/\s+/g," ").trim(),a=s.match(/\b(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)\b\s*:?\s+[^.\n]+/i);let n=a?a[0]:s.split(/[.!?]/)[0];if(!a&&n.length<10){const u=s.split(/[.!?]/).filter(r=>r.trim().length>10);u.length>0&&(n=u[0].trim())}const o=n.toLowerCase();for(const u of Re){const r=u.slice(0,-1);if(o.startsWith(r+" ")&&!o.startsWith(u)){n=r+": "+n.slice(r.length+1);break}}return n=ge(n),!n||n.length<5?null:(n.length>t*1.2&&(n=de(n,t)),n)},"deriveMessageFromReasoning"),T=c(async(e,t,s,a,n,o,u,r,d)=>{const h=a,p=await Te(e,t,[{role:"system",content:fe(s,o,u)},{role:"user",content:h}],.3,1,0,0,Math.max(300,o*12),n,r),i=(p.choices||[]).map(l=>l.message?.content||"").map(l=>ge(l)).filter(Boolean).map(l=>l.length>o*1.1?de(l,o):l).filter(l=>l.length>=10);if(i.length>0)return _e(i);const f=p.choices.map(l=>l.message?.reasoning||"").filter(Boolean);for(const l of f){const m=Ne(l,o);if(m)return[m]}return[]},"generateCommitMessageFromSummary"),je=c(async(e,t=30,s=4e3)=>{try{const a=e.slice(0,5),n=[];let o=s;for(const u of a){const{stdout:r}=await x("git",["diff","--cached","--unified=0","--",u]);if(!r)continue;const d=r.split(` `).filter(Boolean),h=[];let p=0;for(const i of d){const f=i.startsWith("@@"),l=(i.startsWith("+")||i.startsWith("-"))&&!i.startsWith("+++")&&!i.startsWith("---");if((f||l)&&(h.push(i),p++,p>=t))break}if(h.length>0){const i=[`# ${u}`,...h].join(` `);i.length<=o?(n.push(i),o-=i.length):(n.push(i.slice(0,Math.max(0,o))),o=0)}if(o<=0)break}return n.length===0?"":["Context snippets (truncated):",...n].join(` `)}catch{return""}},"buildDiffSnippets"),_=c(async(e,t,s)=>{const a=await je(e,30,3e3);return`Analyze the following git changes and generate a single, complete conventional commit message. CHANGES SUMMARY: ${t} ${a?` CODE CONTEXT: ${a} `:""} TASK: Write ONE conventional commit message that accurately describes what was changed. REQUIREMENTS: - Format: type: subject (NO scope, just type and subject) - Maximum ${s} characters - Be specific and descriptive - Use imperative mood, present tense - Include the main component/area affected - Complete the message - never truncate mid-sentence COMMIT TYPE GUIDELINES: - feat: NEW user-facing features only - refactor: code improvements, restructuring, internal changes - fix: bug fixes that resolve issues - docs: documentation changes only - chore: config updates, maintenance, dependencies EXAMPLES (correct format - NO scope, just type and subject): - feat: add user login with OAuth integration - fix: resolve memory leak in image processing service - refactor: improve message generation with better prompts - refactor: increase default max-length from 50 to 100 - docs: update installation and configuration guide - test: add unit tests for JWT token validation - chore: update axios to v1.6.0 for security patches WRONG FORMAT (do not use): - feat(auth): add user login - refactor(commit): improve prompts Return only the commit message line, no explanations.`},"buildSingleCommitPrompt"),Ye=`\u2554\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2557 \u2502 \u2502 \u2502 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2502 \u2502 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2502 \u2502 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502 \u2502 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502 \u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502 \u2502 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u2502 \u2502 \u2502 \u255A\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u255D`;var Ge=c(async(e,t,s,a,n,o)=>(async()=>{console.log(Ye),console.log(),q(J(X(" lazycommit "))),await ne();const u=G();s&&await x("git",["add","--update"]),u.start("Detecting staged files");const r=await ae(t);if(!r)throw u.stop("Detecting staged files"),new y("No staged changes found. Stage your changes manually, or automatically stage all changes with the `--all` flag.");const d=await ue(t),h=r.diff.length>5e4,p=r.files.length>=5,i=d&&d.fileStats.some(g=>g.changes>500);if((h||p||i)&&d){let g="Large diff detected";p?g="Many files detected":i&&(g="Large file changes detected"),u.stop(`${oe(r.files)} (${d.totalChanges.toLocaleString()} changes): ${r.files.map(w=>` ${w}`).join(` `)} ${g} - using enhanced analysis for better commit message`)}else u.stop(`${oe(r.files)}: ${r.files.map(g=>` ${g}`).join(` `)}`);const{env:l}=process,m=await L({GROQ_API_KEY:l.GROQ_API_KEY,proxy:l.https_proxy||l.HTTPS_PROXY||l.http_proxy||l.HTTP_PROXY,generate:e?.toString(),type:a?.toString()}),Q=G();Q.start("The AI is analyzing your changes");let E;try{const g=await ie(t,25);if(g){const w=await _(r.files,g,m["max-length"]);E=await T(m.GROQ_API_KEY,m.model,m.locale,w,m.generate,m["max-length"],m.type,m.timeout,m.proxy)}else{const w=r.files.join(", "),C=await _(r.files,`Files: ${w}`,m["max-length"]),Xe=fe(m.locale,m["max-length"],m.type);E=await T(m.GROQ_API_KEY,m.model,m.locale,C,m.generate,m["max-length"],m.type,m.timeout,m.proxy)}}finally{Q.stop("Changes analyzed")}if(E.length===0)throw new y("No commit messages were generated. Try again.");let $,N=!1,O=!1;if(E.length===1){[$]=E;const g=await V({message:`Review generated commit message: ${$} `,options:[{label:"Use as-is",value:"use"},{label:"Edit",value:"edit"},{label:"Cancel",value:"cancel"}]});if(k(g)||g==="cancel"){A("Commit cancelled");return}if(g==="use")O=!0;else if(g==="edit"){const w=await Z({message:"Edit commit message:",initialValue:$,validate:c(C=>C&&C.trim().length>0?void 0:"Message cannot be empty","validate")});if(k(w)){A("Commit cancelled");return}$=String(w).trim(),N=!0}}else{const g=await V({message:`Pick a commit message to use: ${Y("(Ctrl+c to exit)")}`,options:E.map(w=>({label:w,value:w}))});if(k(g)){A("Commit cancelled");return}$=g,O=!0}if(!O&&!N){const g=await ee({message:"Edit the commit message before committing?"});if(g&&!k(g)){const w=await Z({message:"Edit commit message:",initialValue:$,validate:c(C=>C&&C.trim().length>0?void 0:"Message cannot be empty","validate")});if(k(w)){A("Commit cancelled");return}$=String(w).trim(),N=!0}}if(!O){const g=await ee({message:`Proceed with this commit message? ${$} `});if(!g||k(g)){A("Commit cancelled");return}}await x("git",["commit","-m",$,...o]),A(`${I("\u2714")} Successfully committed!`)})().catch(u=>{A(`${P("\u2716")} ${u.message}`),S(u),process.exit(1)}),"lazycommit");const[z,Fe]=process.argv.slice(2);var Ue=c(()=>(async()=>{if(!z)throw new y('Commit message file path is missing. This file should be called from the "prepare-commit-msg" git hook');if(Fe)return;const e=await ae();if(!e)return;q(J(X(" lazycommit ")));const{env:t}=process,s=await L({GROQ_API_KEY:t.GROQ_API_KEY,proxy:t.https_proxy||t.HTTPS_PROXY||t.http_proxy||t.HTTP_PROXY}),a=G();a.start("The AI is analyzing your changes");let n;try{const h=await ie();if(h){const p=await _(e.files,h,s["max-length"]);n=await T(s.GROQ_API_KEY,s.model,s.locale,p,s.generate,s["max-length"],s.type,s.timeout,s.proxy)}else{const p=e.files.join(", "),i=await _(e.files,`Files: ${p}`,s["max-length"]);n=await T(s.GROQ_API_KEY,s.model,s.locale,i,s.generate,s["max-length"],s.type,s.timeout,s.proxy)}}finally{a.stop("Changes analyzed")}const u=await b.readFile(z,"utf8")!=="",r=n.length>1;let d="";u&&(d=`# \u{1F916} AI generated commit${r?"s":""} `),r?(u&&(d+=`# Select one of the following messages by uncommeting: `),d+=` ${n.map(h=>`# ${h}`).join(` `)}`):(u&&(d+=`# Edit the message below and commit: `),d+=` ${n[0]} `),await b.appendFile(z,d),A(`${I("\u2714")} Saved commit message!`)})().catch(e=>{A(`${P("\u2716")} ${e.message}`),S(e),process.exit(1)}),"prepareCommitMessageHook"),We=K({name:"config",parameters:["<mode>","<key=value...>"]},e=>{(async()=>{const{mode:t,keyValue:s}=e._;if(t==="get"){const a=await L({},!0);for(const n of s)ce(a,n)&&console.log(`${n}=${a[n]}`);return}if(t==="set"){await De(s.map(a=>a.split("=")));return}throw new y(`Invalid mode: ${t}`)})().catch(t=>{console.error(`${P("\u2716")} ${t.message}`),S(t),process.exit(1)})});const pe="prepare-commit-msg",he=`.git/hooks/${pe}`,R=B(new URL("cli.mjs",import.meta.url)),Le=process.argv[1].replace(/\\/g,"/").endsWith(`/${he}`),ye=process.platform==="win32",we=` #!/usr/bin/env node import(${JSON.stringify(xe(R))}) `.trim();var ze=K({name:"hook",parameters:["<install/uninstall>"]},e=>{(async()=>{const t=await ne(),{installUninstall:s}=e._,a=j.join(t,he),n=await re(a);if(s==="install"){if(n){if(await b.realpath(a).catch(()=>{})===R){console.warn("The hook is already installed");return}throw new y(`A different ${pe} hook seems to be installed. Please remove it before installing lazycommit.`)}await b.mkdir(j.dirname(a),{recursive:!0}),ye?await b.writeFile(a,we):(await b.symlink(R,a,"file"),await b.chmod(a,493)),console.log(`${I("\u2714")} Hook installed`);return}if(s==="uninstall"){if(!n){console.warn("Hook is not installed");return}if(ye){if(await b.readFile(a,"utf8")!==we){console.warn("Hook is not installed");return}}else if(await b.realpath(a)!==R){console.warn("Hook is not installed");return}await b.rm(a),console.log(`${I("\u2714")} Hook uninstalled`);return}throw new y(`Invalid mode: ${s}`)})().catch(t=>{console.error(`${P("\u2716")} ${t.message}`),S(t),process.exit(1)})});const He=B(import.meta.url),Qe=Ce(He),Ke=JSON.parse(Ae(Ee(Qe,"../package.json"),"utf8")),{description:Be,version:Je}=Ke,be=process.argv.slice(2);$e({name:"lazycommit",version:Je,flags:{generate:{type:Number,description:"Number of messages to generate (Warning: generating multiple costs more) (default: 1)",alias:"g"},exclude:{type:[String],description:"Files to exclude from AI analysis",alias:"x"},all:{type:Boolean,description:"Automatically stage changes in tracked files for the commit",alias:"a",default:!1},type:{type:String,description:"Type of commit message to generate",alias:"t"},split:{type:Boolean,description:"Create multiple commits by grouping files logically",alias:"s",default:!1}},commands:[We,ze],help:{description:Be},ignoreArgv:c(e=>e==="unknown-flag"||e==="argument","ignoreArgv")},e=>{Le?Ue():Ge(e.flags.generate,e.flags.exclude,e.flags.all,e.flags.type,e.flags.split,be)},be);