lazycommitt 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +78 -5
  2. package/dist/cli.mjs +48 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  <h1 align="center">lazycommit</h1>
4
4
  </div>
5
5
  <p>A CLI that writes your git commit messages for you with AI using Groq. Never write a commit message again.</p>
6
- <a href="https://www.npmjs.com/package/lazycommitz"><img src="https://img.shields.io/npm/v/lazycommitz" alt="Current version"></a>
6
+ <a href="https://www.npmjs.com/package/lazycommitz"><img src="https://img.shields.io/npm/v/lazycommitt" alt="Current version"></a>
7
7
  <a href="https://github.com/KartikLabhshetwar/lazycommit"><img src="https://img.shields.io/github/stars/KartikLabhshetwar/lazycommit" alt="GitHub stars"></a>
8
- <a href="https://github.com/KartikLabhshetwar/lazycommit/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/lazycommitz" alt="License"></a>
8
+ <a href="https://github.com/KartikLabhshetwar/lazycommit/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/lazycommitt" alt="License"></a>
9
9
  </div>
10
10
 
11
11
  ---
@@ -17,7 +17,7 @@
17
17
  1. Install _lazycommit_:
18
18
 
19
19
  ```sh
20
- npm install -g lazycommitz
20
+ npm install -g lazycommitt
21
21
  ```
22
22
 
23
23
  2. Retrieve your API key from [Groq Console](https://console.groq.com/keys)
@@ -43,7 +43,7 @@ lazycommit --version
43
43
  If it's not the [latest version](https://github.com/KartikLabhshetwar/lazycommit/releases/latest), run:
44
44
 
45
45
  ```sh
46
- npm update -g lazycommitz
46
+ npm update -g lazycommitt
47
47
  ```
48
48
 
49
49
  ## Usage
@@ -95,6 +95,15 @@ You can exclude specific files from AI analysis using the `--exclude` flag:
95
95
  lazycommit --exclude package-lock.json --exclude dist/
96
96
  ```
97
97
 
98
+ #### Handling large diffs
99
+
100
+ For large commits with many files, lazycommit automatically uses intelligent chunking to stay within API limits:
101
+
102
+ - **Automatic detection**: Large diffs (>50KB) are automatically detected
103
+ - **Smart chunking**: Diffs are split into manageable pieces (default: 6000 tokens)
104
+ - **Chunked processing**: Each chunk is analyzed separately, then combined
105
+ - **Progress indicators**: Clear feedback during chunked processing
106
+
98
107
  ### Git hook
99
108
 
100
109
  You can also integrate _lazycommit_ with Git via the [`prepare-commit-msg`](https://git-scm.com/docs/githooks#_prepare_commit_msg) hook. This lets you use Git like you normally would, and edit the commit message before committing.
@@ -244,12 +253,76 @@ You can clear this option by setting it to an empty string:
244
253
  lazycommit config set type=
245
254
  ```
246
255
 
256
+ #### chunk-size
257
+
258
+ Default: `6000`
259
+
260
+ The maximum number of tokens per chunk when processing large diffs. This helps avoid API rate limits:
261
+
262
+ ```sh
263
+ lazycommit config set chunk-size 4000
264
+ ```
265
+
266
+ **Note**: Must be between 1000-8000 tokens (Groq API limit).
267
+
247
268
  ## How it works
248
269
 
249
270
  This CLI tool runs `git diff` to grab all your latest code changes, sends them to Groq's AI models, then returns the AI generated commit message.
250
271
 
251
272
  The tool uses Groq's fast inference API to provide quick and accurate commit message suggestions based on your code changes.
252
273
 
274
+ ### Large diff handling
275
+
276
+ For large commits that exceed API token limits, lazycommit automatically:
277
+
278
+ 1. **Detects large diffs** (>50KB or ~12.5k tokens)
279
+ 2. **Chunks the diff** into manageable pieces (default: 6000 tokens)
280
+ 3. **Processes each chunk** separately to generate partial commit messages
281
+ 4. **Combines results** into a single, comprehensive commit message
282
+ 5. **Falls back gracefully** if chunking fails, showing individual chunk messages
283
+
284
+ This ensures you can commit large changes (like new features, refactoring, or initial project setup) without hitting API limits.
285
+
286
+ ## Troubleshooting
287
+
288
+ ### "Request too large" error (413)
289
+
290
+ If you get a 413 error, your diff is too large for the API. Try these solutions:
291
+
292
+ 1. **Exclude build artifacts**:
293
+ ```sh
294
+ lazycommit --exclude "dist/**" --exclude "node_modules/**" --exclude ".next/**"
295
+ ```
296
+
297
+ 2. **Reduce chunk size**:
298
+ ```sh
299
+ lazycommit config set chunk-size 4000
300
+ ```
301
+
302
+ 3. **Use a different model**:
303
+ ```sh
304
+ lazycommit config set model "llama-3.1-70b-versatile"
305
+ ```
306
+
307
+ 4. **Commit in smaller batches**:
308
+ ```sh
309
+ git add src/ # Stage only source files
310
+ lazycommit
311
+ git add docs/ # Then stage documentation
312
+ lazycommit
313
+ ```
314
+
315
+ ### No commit messages generated
316
+
317
+ - Check your API key: `lazycommit config get GROQ_API_KEY`
318
+ - Verify you have staged changes: `git status`
319
+ - Try reducing chunk size or excluding large files
320
+
321
+ ### Slow performance with large diffs
322
+
323
+ - Reduce chunk size: `lazycommit config set chunk-size 4000`
324
+ - Exclude unnecessary files: `lazycommit --exclude "*.log" --exclude "*.tmp"`
325
+
253
326
  ## Why Groq?
254
327
 
255
328
  - **Fast**: Groq provides ultra-fast inference speeds
@@ -267,4 +340,4 @@ If you want to help fix a bug or implement a feature in [Issues](https://github.
267
340
 
268
341
  ## License
269
342
 
270
- This project is licensed under the Apache-2.0 License - see the [LICENSE](LICENSE) file for details.
343
+ This project is licensed under the Apache-2.0 License - see the [LICENSE](LICENSE) file for details.
package/dist/cli.mjs CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env node
2
- var se=Object.defineProperty;var n=(e,t)=>se(e,"name",{value:t,configurable:!0});import{command as S,cli as oe}from"cleye";import{readFileSync as ae}from"fs";import{fileURLToPath as F,pathToFileURL as ne}from"url";import P,{dirname as ie,join as re}from"path";import{execa as $}from"execa";import{dim as C,bgCyan as z,black as G,green as b,red as v}from"kolorist";import{intro as Y,spinner as _,confirm as ce,isCancel as N,outro as w,select as le}from"@clack/prompts";import p from"fs/promises";import me from"os";import j from"ini";import U from"groq-sdk";var fe="1.0.0",ge={version:fe};const R=class R extends Error{};n(R,"KnownError");let m=R;const I=" ",k=n(e=>{e instanceof Error&&!(e instanceof m)&&(e.stack&&console.error(C(e.stack.split(`
2
+ var oe=Object.defineProperty;var c=(e,t)=>oe(e,"name",{value:t,configurable:!0});import{command as N,cli as ie}from"cleye";import{readFileSync as re}from"fs";import{fileURLToPath as Y,pathToFileURL as ce}from"url";import M,{dirname as le,join as me}from"path";import{execa as $}from"execa";import{dim as I,bgCyan as j,black as U,green as v,red as P}from"kolorist";import{intro as D,spinner as E,confirm as ge,isCancel as H,outro as b,select as fe}from"@clack/prompts";import y from"fs/promises";import he from"os";import Q from"ini";import L from"groq-sdk";var ue="1.0.2",de={version:ue};const G=class G extends Error{};c(G,"KnownError");let f=G;const O=" ",A=c(e=>{e instanceof Error&&!(e instanceof f)&&(e.stack&&console.error(I(e.stack.split(`
3
3
  `).slice(1).join(`
4
4
  `))),console.error(`
5
- ${I}${C(`lazycommit v${ge.version}`)}`),console.error(`
6
- ${I}Please open a Bug report with the information above:`),console.error(`${I}https://github.com/KartikLabhshetwar/lazycommit/issues/new/choose`))},"handleCliError"),H=n(async()=>{const{stdout:e,failed:t}=await $("git",["rev-parse","--show-toplevel"],{reject:!1});if(t)throw new m("The current directory must be a Git repository!");return e},"assertGitRepo"),O=n(e=>`:(exclude)${e}`,"excludeFromDiff"),D=["package-lock.json","pnpm-lock.yaml","*.lock"].map(O),Q=n(async e=>{const t=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await $("git",[...t,"--name-only",...D,...e?e.map(O):[]]);if(!s)return;const{stdout:o}=await $("git",[...t,...D,...e?e.map(O):[]]);return{files:s.split(`
7
- `),diff:o}},"getStagedDiff"),pe=n(e=>`Detected ${e.length.toLocaleString()} staged file${e.length>1?"s":""}`,"getDetectedMessage"),K=n(e=>p.lstat(e).then(()=>!0,()=>!1),"fileExists"),de=["","conventional"],{hasOwnProperty:he}=Object.prototype,J=n((e,t)=>he.call(e,t),"hasOwn"),d=n((e,t,s)=>{if(!t)throw new m(`Invalid config property ${e}: ${s}`)},"parseAssert"),x={GROQ_API_KEY(e){if(!e)throw new m("Please set your Groq API key via `lazycommit config set GROQ_API_KEY=<your token>`");return d("GROQ_API_KEY",e.startsWith("gsk_"),'Must start with "gsk_"'),e},locale(e){return e?(d("locale",e,"Cannot be empty"),d("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;d("generate",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return d("generate",t>0,"Must be greater than 0"),d("generate",t<=5,"Must be less or equal to 5"),t},type(e){return e?(d("type",de.includes(e),"Invalid commit type"),e):""},proxy(e){if(!(!e||e.length===0))return d("proxy",/^https?:\/\//.test(e),"Must be a valid URL"),e},model(e){return!e||e.length===0?"openai/gpt-oss-120b":e},timeout(e){if(!e)return 1e4;d("timeout",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return d("timeout",t>=500,"Must be greater than 500ms"),t},"max-length"(e){if(!e)return 50;d("max-length",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return d("max-length",t>=20,"Must be greater than 20 characters"),t}},T=P.join(me.homedir(),".lazycommit"),L=n(async()=>{if(!await K(T))return Object.create(null);const t=await p.readFile(T,"utf8");return j.parse(t)},"readConfigFile"),E=n(async(e,t)=>{const s=await L(),o={};for(const a of Object.keys(x)){const c=x[a],l=e?.[a]??s[a];if(t)try{o[a]=c(l)}catch{}else o[a]=c(l)}return o},"getConfig"),ue=n(async e=>{const t=await L();for(const[s,o]of e){if(!J(x,s))throw new m(`Invalid config property: ${s}`);const a=x[s](o);t[s]=a}await p.writeFile(T,j.stringify(t),"utf8")},"setConfigs"),ye={"":"<commit message>",conventional:"<type>(<optional scope>): <commit message>"},W={"":"",conventional:`Choose the most appropriate type from the following categories that best describes the git diff:
5
+ ${O}${I(`lazycommit v${de.version}`)}`),console.error(`
6
+ ${O}Please open a Bug report with the information above:`),console.error(`${O}https://github.com/KartikLabhshetwar/lazycommit/issues/new/choose`))},"handleCliError"),q=c(async()=>{const{stdout:e,failed:t}=await $("git",["rev-parse","--show-toplevel"],{reject:!1});if(t)throw new f("The current directory must be a Git repository!");return e},"assertGitRepo"),x=c(e=>`:(exclude)${e}`,"excludeFromDiff"),F=["package-lock.json","pnpm-lock.yaml","*.lock"].map(x),J=c(async e=>{const t=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await $("git",[...t,"--name-only",...F,...e?e.map(x):[]]);if(!s)return;const{stdout:n}=await $("git",[...t,...F,...e?e.map(x):[]]);return{files:s.split(`
7
+ `),diff:n}},"getStagedDiff"),K=c(e=>`Detected ${e.length.toLocaleString()} staged file${e.length>1?"s":""}`,"getDetectedMessage"),W=c(e=>Math.ceil(e.length/4),"estimateTokenCount"),pe=c((e,t=6e3)=>{if(W(e)<=t)return[e];const n=[],o=e.split(`
8
+ `);let a="",i=0;for(const g of o){const m=W(g);i+m>t&&a.length>0?(n.push(a.trim()),a=g+`
9
+ `,i=m):(a+=g+`
10
+ `,i+=m)}return a.trim().length>0&&n.push(a.trim()),n},"chunkDiff"),we=c(async e=>{const t=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await $("git",[...t,"--name-only",...F,...e?e.map(x):[]]);if(!s)return null;const n=s.split(`
11
+ `).filter(Boolean),o=await Promise.all(n.map(async a=>{try{const{stdout:i}=await $("git",[...t,"--numstat","--",a]),[g,m]=i.split(" ").slice(0,2).map(Number);return{file:a,additions:g||0,deletions:m||0,changes:(g||0)+(m||0)}}catch{return{file:a,additions:0,deletions:0,changes:0}}}));return{files:n,fileStats:o,totalChanges:o.reduce((a,i)=>a+i.changes,0)}},"getDiffSummary"),B=c(e=>y.lstat(e).then(()=>!0,()=>!1),"fileExists"),ye=["","conventional"],{hasOwnProperty:ke}=Object.prototype,X=c((e,t)=>ke.call(e,t),"hasOwn"),p=c((e,t,s)=>{if(!t)throw new f(`Invalid config property ${e}: ${s}`)},"parseAssert"),C={GROQ_API_KEY(e){if(!e)throw new f("Please set your Groq API key via `lazycommit config set GROQ_API_KEY=<your token>`");return p("GROQ_API_KEY",e.startsWith("gsk_"),'Must start with "gsk_"'),e},locale(e){return e?(p("locale",e,"Cannot be empty"),p("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;p("generate",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return p("generate",t>0,"Must be greater than 0"),p("generate",t<=5,"Must be less or equal to 5"),t},type(e){return e?(p("type",ye.includes(e),"Invalid commit type"),e):""},proxy(e){if(!(!e||e.length===0))return p("proxy",/^https?:\/\//.test(e),"Must be a valid URL"),e},model(e){return!e||e.length===0?"openai/gpt-oss-120b":e},timeout(e){if(!e)return 1e4;p("timeout",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return p("timeout",t>=500,"Must be greater than 500ms"),t},"max-length"(e){if(!e)return 50;p("max-length",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return p("max-length",t>=20,"Must be greater than 20 characters"),t},"chunk-size"(e){if(!e)return 6e3;p("chunk-size",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return p("chunk-size",t>=1e3,"Must be at least 1000 tokens"),p("chunk-size",t<=8e3,"Must be at most 8000 tokens (Groq limit)"),t}},R=M.join(he.homedir(),".lazycommit"),V=c(async()=>{if(!await B(R))return Object.create(null);const t=await y.readFile(R,"utf8");return Q.parse(t)},"readConfigFile"),S=c(async(e,t)=>{const s=await V(),n={};for(const o of Object.keys(C)){const a=C[o],i=e?.[o]??s[o];if(t)try{n[o]=a(i)}catch{}else n[o]=a(i)}return n},"getConfig"),$e=c(async e=>{const t=await V();for(const[s,n]of e){if(!X(C,s))throw new f(`Invalid config property: ${s}`);const o=C[s](n);t[s]=o}await y.writeFile(R,Q.stringify(t),"utf8")},"setConfigs"),be={"":"<commit message>",conventional:"<type>(<optional scope>): <commit message>"},Z={"":"",conventional:`Choose the most appropriate type from the following categories that best describes the git diff:
8
12
 
9
13
  ${JSON.stringify({feat:"A new feature for the user",fix:"A bug fix",docs:"Documentation only changes",style:"Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)",refactor:"A code change that neither fixes a bug nor adds a feature",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:"Other changes that don't modify src or test files",revert:"Reverts a previous commit"},null,2)}
10
14
 
11
- IMPORTANT: Use the exact type name from the list above.`},we=n((e,t,s)=>`You are an expert software engineer and git commit message writer. Your task is to analyze git diffs and generate clear, concise, and professional commit messages.
15
+ IMPORTANT: Use the exact type name from the list above.`},ve=c((e,t,s)=>`You are an expert software engineer and git commit message writer. Your task is to analyze git diffs and generate clear, concise, and professional commit messages.
12
16
 
13
17
  ## Instructions:
14
18
  1. Analyze the provided git diff carefully
@@ -29,11 +33,11 @@ IMPORTANT: Use the exact type name from the list above.`},we=n((e,t,s)=>`You are
29
33
 
30
34
  ## Language: ${e}
31
35
  ## Maximum length: ${t} characters
32
- ## Output format: ${ye[s]||"<commit message>"}
36
+ ## Output format: ${be[s]||"<commit message>"}
33
37
 
34
- ${W[s]?`
38
+ ${Z[s]?`
35
39
  ## Commit Type Guidelines:
36
- ${W[s]}`:""}
40
+ ${Z[s]}`:""}
37
41
 
38
42
  ## Examples of good commit messages:
39
43
  - "feat: Add user authentication with JWT tokens"
@@ -50,23 +54,43 @@ ${W[s]}`:""}
50
54
  - "WIP"
51
55
  - "Stuff"
52
56
 
53
- Remember: Your response will be used directly as the git commit message. Make it professional and informative.`,"generatePrompt"),$e=n(async(e,t,s,o,a,c,l,g,i,h,f)=>{const u=new U({apiKey:e,timeout:h});try{return i>1?{choices:(await Promise.all(Array.from({length:i},()=>u.chat.completions.create({model:t,messages:s,temperature:o,top_p:a,frequency_penalty:c,presence_penalty:l,max_tokens:g,n:1})))).flatMap(te=>te.choices)}:await u.chat.completions.create({model:t,messages:s,temperature:o,top_p:a,frequency_penalty:c,presence_penalty:l,max_tokens:g,n:1})}catch(r){if(r instanceof U.APIError){let y=`Groq API Error: ${r.status} - ${r.name}`;throw r.message&&(y+=`
57
+ Remember: Your response will be used directly as the git commit message. Make it professional and informative.`,"generatePrompt"),Pe=c(async(e,t,s,n,o,a,i,g,m,u,l)=>{const w=new L({apiKey:e,timeout:u});try{return m>1?{choices:(await Promise.all(Array.from({length:m},()=>w.chat.completions.create({model:t,messages:s,temperature:n,top_p:o,frequency_penalty:a,presence_penalty:i,max_tokens:g,n:1})))).flatMap(d=>d.choices)}:await w.chat.completions.create({model:t,messages:s,temperature:n,top_p:o,frequency_penalty:a,presence_penalty:i,max_tokens:g,n:1})}catch(r){if(r instanceof L.APIError){let h=`Groq API Error: ${r.status} - ${r.name}`;throw r.message&&(h+=`
54
58
 
55
- ${r.message}`),r.status===500&&(y+=`
59
+ ${r.message}`),r.status===500&&(h+=`
56
60
 
57
- Check the API status: https://console.groq.com/status`),new m(y)}throw r.code==="ENOTFOUND"?new m(`Error connecting to ${r.hostname} (${r.syscall}). Are you connected to the internet?`):r}},"createChatCompletion"),be=n(e=>e.trim().replace(/[\n\r]/g,"").replace(/(\w)\.$/,"$1"),"sanitizeMessage"),ve=n(e=>Array.from(new Set(e)),"deduplicateMessages"),q=n(async(e,t,s,o,a,c,l,g,i)=>{try{const h=await $e(e,t,[{role:"system",content:we(s,c,l)},{role:"user",content:o}],.7,1,0,0,200,a,g,i);return ve(h.choices.filter(f=>f.message?.content).map(f=>be(f.message.content)))}catch(h){const f=h;throw f.code==="ENOTFOUND"?new m(`Error connecting to ${f.hostname} (${f.syscall}). Are you connected to the internet?`):f}},"generateCommitMessage");var ke=n(async(e,t,s,o,a)=>(async()=>{Y(z(G(" lazycommit "))),await H();const c=_();s&&await $("git",["add","--update"]),c.start("Detecting staged files");const l=await Q(t);if(!l)throw c.stop("Detecting staged files"),new m("No staged changes found. Stage your changes manually, or automatically stage all changes with the `--all` flag.");c.stop(`${pe(l.files)}:
58
- ${l.files.map(r=>` ${r}`).join(`
59
- `)}`);const{env:g}=process,i=await E({GROQ_API_KEY:g.GROQ_API_KEY,proxy:g.https_proxy||g.HTTPS_PROXY||g.http_proxy||g.HTTP_PROXY,generate:e?.toString(),type:o?.toString()}),h=_();h.start("The AI is analyzing your changes");let f;try{f=await q(i.GROQ_API_KEY,i.model,i.locale,l.diff,i.generate,i["max-length"],i.type,i.timeout,i.proxy)}finally{h.stop("Changes analyzed")}if(f.length===0)throw new m("No commit messages were generated. Try again.");let u;if(f.length===1){[u]=f;const r=await ce({message:`Use this commit message?
61
+ Check the API status: https://console.groq.com/status`),(r.status===413||r.message&&r.message.includes("rate_limit_exceeded"))&&(h+=`
60
62
 
61
- ${u}
62
- `});if(!r||N(r)){w("Commit cancelled");return}}else{const r=await le({message:`Pick a commit message to use: ${C("(Ctrl+c to exit)")}`,options:f.map(y=>({label:y,value:y}))});if(N(r)){w("Commit cancelled");return}u=r}await $("git",["commit","-m",u,...a]),w(`${b("\u2714")} Successfully committed!`)})().catch(c=>{w(`${v("\u2716")} ${c.message}`),k(c),process.exit(1)}),"lazycommit");const[M,xe]=process.argv.slice(2);var Ae=n(()=>(async()=>{if(!M)throw new m('Commit message file path is missing. This file should be called from the "prepare-commit-msg" git hook');if(xe)return;const e=await Q();if(!e)return;Y(z(G(" lazycommit ")));const{env:t}=process,s=await E({GROQ_API_KEY:t.GROQ_API_KEY,proxy:t.https_proxy||t.HTTPS_PROXY||t.http_proxy||t.HTTP_PROXY}),o=_();o.start("The AI is analyzing your changes");let a;try{a=await q(s.GROQ_API_KEY,s.model,s.locale,e.diff,s.generate,s["max-length"],s.type,s.timeout,s.proxy)}finally{o.stop("Changes analyzed")}const l=await p.readFile(M,"utf8")!=="",g=a.length>1;let i="";l&&(i=`# \u{1F916} AI generated commit${g?"s":""}
63
- `),g?(l&&(i+=`# Select one of the following messages by uncommeting:
64
- `),i+=`
65
- ${a.map(h=>`# ${h}`).join(`
66
- `)}`):(l&&(i+=`# Edit the message below and commit:
67
- `),i+=`
68
- ${a[0]}
69
- `),await p.appendFile(M,i),w(`${b("\u2714")} Saved commit message!`)})().catch(e=>{w(`${v("\u2716")} ${e.message}`),k(e),process.exit(1)}),"prepareCommitMessageHook"),Pe=S({name:"config",parameters:["<mode>","<key=value...>"]},e=>{(async()=>{const{mode:t,keyValue:s}=e._;if(t==="get"){const o=await E({},!0);for(const a of s)J(o,a)&&console.log(`${a}=${o[a]}`);return}if(t==="set"){await ue(s.map(o=>o.split("=")));return}throw new m(`Invalid mode: ${t}`)})().catch(t=>{console.error(`${v("\u2716")} ${t.message}`),k(t),process.exit(1)})});const B="prepare-commit-msg",X=`.git/hooks/${B}`,A=F(new URL("cli.mjs",import.meta.url)),Ce=process.argv[1].replace(/\\/g,"/").endsWith(`/${X}`),V=process.platform==="win32",Z=`
63
+ \u{1F4A1} Tip: Your diff is too large. Try:
64
+ 1. Commit files in smaller batches
65
+ 2. Exclude large files with --exclude
66
+ 3. Use a different model with --model
67
+ 4. Check if you have build artifacts staged (dist/, .next/, etc.)`),new f(h)}throw r.code==="ENOTFOUND"?new f(`Error connecting to ${r.hostname} (${r.syscall}). Are you connected to the internet?`):r}},"createChatCompletion"),Ae=c(e=>e.trim().replace(/[\n\r]/g,"").replace(/(\w)\.$/,"$1"),"sanitizeMessage"),xe=c(e=>Array.from(new Set(e)),"deduplicateMessages"),T=c(async(e,t,s,n,o,a,i,g,m)=>{try{const u=await Pe(e,t,[{role:"system",content:ve(s,a,i)},{role:"user",content:n}],.7,1,0,0,Math.max(200,a*8),o,g,m);return xe(u.choices.filter(l=>l.message?.content).map(l=>Ae(l.message.content)))}catch(u){const l=u;throw l.code==="ENOTFOUND"?new f(`Error connecting to ${l.hostname} (${l.syscall}). Are you connected to the internet?`):l}},"generateCommitMessage"),Ce=c(async(e,t,s,n,o,a,i,g,m,u=6e3)=>{const l=pe(n,u);if(l.length===1)try{return await T(e,t,s,n,o,a,i,g,m)}catch(r){throw new f(`Failed to generate commit message: ${r instanceof Error?r.message:"Unknown error"}`)}const w=[];for(let r=0;r<l.length;r++){const h=l[r],d=`This is part ${r+1} of ${l.length} of a large diff.
68
+ Please generate a commit message that describes the changes in this specific part.
69
+ Focus on the most significant changes in this chunk.
70
+
71
+ ${h}`;try{const k=await T(e,t,s,d,1,a,i,g,m);k.length>0&&w.push(k[0])}catch(k){console.warn(`Failed to process chunk ${r+1}:`,k instanceof Error?k.message:"Unknown error")}}if(w.length===0)throw new f("Failed to generate commit messages for any chunks");if(w.length>1){const r=`I have ${w.length} commit messages for different parts of a large change:
72
+
73
+ ${w.map((h,d)=>`${d+1}. ${h}`).join(`
74
+ `)}
75
+
76
+ Please generate a single, comprehensive commit message that captures the overall changes.
77
+ The message should be concise but cover the main aspects of all the changes.`;try{return await T(e,t,s,r,o,a,i,g,m)}catch{return w}}return w},"generateCommitMessageFromChunks");var Te=c(async(e,t,s,n,o)=>(async()=>{D(j(U(" lazycommit "))),await q();const a=E();s&&await $("git",["add","--update"]),a.start("Detecting staged files");const i=await J(t);if(!i)throw a.stop("Detecting staged files"),new f("No staged changes found. Stage your changes manually, or automatically stage all changes with the `--all` flag.");const g=await we(t);i.diff.length>5e4&&g?a.stop(`${K(i.files)} (${g.totalChanges.toLocaleString()} changes):
78
+ ${i.files.map(d=>` ${d}`).join(`
79
+ `)}
80
+
81
+ \u26A0\uFE0F Large diff detected - using chunked processing`):a.stop(`${K(i.files)}:
82
+ ${i.files.map(d=>` ${d}`).join(`
83
+ `)}`);const{env:u}=process,l=await S({GROQ_API_KEY:u.GROQ_API_KEY,proxy:u.https_proxy||u.HTTPS_PROXY||u.http_proxy||u.HTTP_PROXY,generate:e?.toString(),type:n?.toString()}),w=E();w.start("The AI is analyzing your changes");let r;try{r=await Ce(l.GROQ_API_KEY,l.model,l.locale,i.diff,l.generate,l["max-length"],l.type,l.timeout,l.proxy,l["chunk-size"])}finally{w.stop("Changes analyzed")}if(r.length===0)throw new f("No commit messages were generated. Try again.");let h;if(r.length===1){[h]=r;const d=await ge({message:`Use this commit message?
84
+
85
+ ${h}
86
+ `});if(!d||H(d)){b("Commit cancelled");return}}else{const d=await fe({message:`Pick a commit message to use: ${I("(Ctrl+c to exit)")}`,options:r.map(k=>({label:k,value:k}))});if(H(d)){b("Commit cancelled");return}h=d}await $("git",["commit","-m",h,...o]),b(`${v("\u2714")} Successfully committed!`)})().catch(a=>{b(`${P("\u2716")} ${a.message}`),A(a),process.exit(1)}),"lazycommit");const[z,_e]=process.argv.slice(2);var Me=c(()=>(async()=>{if(!z)throw new f('Commit message file path is missing. This file should be called from the "prepare-commit-msg" git hook');if(_e)return;const e=await J();if(!e)return;D(j(U(" lazycommit ")));const{env:t}=process,s=await S({GROQ_API_KEY:t.GROQ_API_KEY,proxy:t.https_proxy||t.HTTPS_PROXY||t.http_proxy||t.HTTP_PROXY}),n=E();n.start("The AI is analyzing your changes");let o;try{o=await T(s.GROQ_API_KEY,s.model,s.locale,e.diff,s.generate,s["max-length"],s.type,s.timeout,s.proxy)}finally{n.stop("Changes analyzed")}const i=await y.readFile(z,"utf8")!=="",g=o.length>1;let m="";i&&(m=`# \u{1F916} AI generated commit${g?"s":""}
87
+ `),g?(i&&(m+=`# Select one of the following messages by uncommeting:
88
+ `),m+=`
89
+ ${o.map(u=>`# ${u}`).join(`
90
+ `)}`):(i&&(m+=`# Edit the message below and commit:
91
+ `),m+=`
92
+ ${o[0]}
93
+ `),await y.appendFile(z,m),b(`${v("\u2714")} Saved commit message!`)})().catch(e=>{b(`${P("\u2716")} ${e.message}`),A(e),process.exit(1)}),"prepareCommitMessageHook"),Ie=N({name:"config",parameters:["<mode>","<key=value...>"]},e=>{(async()=>{const{mode:t,keyValue:s}=e._;if(t==="get"){const n=await S({},!0);for(const o of s)X(n,o)&&console.log(`${o}=${n[o]}`);return}if(t==="set"){await $e(s.map(n=>n.split("=")));return}throw new f(`Invalid mode: ${t}`)})().catch(t=>{console.error(`${P("\u2716")} ${t.message}`),A(t),process.exit(1)})});const ee="prepare-commit-msg",te=`.git/hooks/${ee}`,_=Y(new URL("cli.mjs",import.meta.url)),Ee=process.argv[1].replace(/\\/g,"/").endsWith(`/${te}`),se=process.platform==="win32",ne=`
70
94
  #!/usr/bin/env node
71
- import(${JSON.stringify(ne(A))})
72
- `.trim();var _e=S({name:"hook",parameters:["<install/uninstall>"]},e=>{(async()=>{const t=await H(),{installUninstall:s}=e._,o=P.join(t,X),a=await K(o);if(s==="install"){if(a){if(await p.realpath(o).catch(()=>{})===A){console.warn("The hook is already installed");return}throw new m(`A different ${B} hook seems to be installed. Please remove it before installing lazycommit.`)}await p.mkdir(P.dirname(o),{recursive:!0}),V?await p.writeFile(o,Z):(await p.symlink(A,o,"file"),await p.chmod(o,493)),console.log(`${b("\u2714")} Hook installed`);return}if(s==="uninstall"){if(!a){console.warn("Hook is not installed");return}if(V){if(await p.readFile(o,"utf8")!==Z){console.warn("Hook is not installed");return}}else if(await p.realpath(o)!==A){console.warn("Hook is not installed");return}await p.rm(o),console.log(`${b("\u2714")} Hook uninstalled`);return}throw new m(`Invalid mode: ${s}`)})().catch(t=>{console.error(`${v("\u2716")} ${t.message}`),k(t),process.exit(1)})});const Ie=F(import.meta.url),Oe=ie(Ie),Te=JSON.parse(ae(re(Oe,"../package.json"),"utf8")),{description:Ee,version:Me}=Te,ee=process.argv.slice(2);oe({name:"lazycommit",version:Me,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"}},commands:[Pe,_e],help:{description:Ee},ignoreArgv:n(e=>e==="unknown-flag"||e==="argument","ignoreArgv")},e=>{Ce?Ae():ke(e.flags.generate,e.flags.exclude,e.flags.all,e.flags.type,ee)},ee);
95
+ import(${JSON.stringify(ce(_))})
96
+ `.trim();var Oe=N({name:"hook",parameters:["<install/uninstall>"]},e=>{(async()=>{const t=await q(),{installUninstall:s}=e._,n=M.join(t,te),o=await B(n);if(s==="install"){if(o){if(await y.realpath(n).catch(()=>{})===_){console.warn("The hook is already installed");return}throw new f(`A different ${ee} hook seems to be installed. Please remove it before installing lazycommit.`)}await y.mkdir(M.dirname(n),{recursive:!0}),se?await y.writeFile(n,ne):(await y.symlink(_,n,"file"),await y.chmod(n,493)),console.log(`${v("\u2714")} Hook installed`);return}if(s==="uninstall"){if(!o){console.warn("Hook is not installed");return}if(se){if(await y.readFile(n,"utf8")!==ne){console.warn("Hook is not installed");return}}else if(await y.realpath(n)!==_){console.warn("Hook is not installed");return}await y.rm(n),console.log(`${v("\u2714")} Hook uninstalled`);return}throw new f(`Invalid mode: ${s}`)})().catch(t=>{console.error(`${P("\u2716")} ${t.message}`),A(t),process.exit(1)})});const Fe=Y(import.meta.url),Re=le(Fe),Se=JSON.parse(re(me(Re,"../package.json"),"utf8")),{description:ze,version:Ge}=Se,ae=process.argv.slice(2);ie({name:"lazycommit",version:Ge,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"}},commands:[Ie,Oe],help:{description:ze},ignoreArgv:c(e=>e==="unknown-flag"||e==="argument","ignoreArgv")},e=>{Ee?Me():Te(e.flags.generate,e.flags.exclude,e.flags.all,e.flags.type,ae)},ae);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazycommitt",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Writes your git commit messages for you with AI using Groq",
5
5
  "main": "index.js",
6
6
  "keywords": [