lazycommitt 1.0.10 → 1.0.11

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 +50 -35
  2. package/dist/cli.mjs +41 -51
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -109,14 +109,33 @@ You can exclude specific files from AI analysis using the `--exclude` flag:
109
109
  lazycommit --exclude package-lock.json --exclude dist/
110
110
  ```
111
111
 
112
+ #### Automatic multi-commit mode
113
+
114
+ When you stage many files, `lazycommit` can automatically split your changes into logical groups and create multiple commits with proper Conventional Commit messages.
115
+
116
+ - Auto-trigger: when staged files ≥ 5, or when the diff is large
117
+ - Grouping: buckets by type/scope (e.g., `feat(api)`, `docs`, `ci`, `build`, `test`, `chore`)
118
+ - Deep split: if everything falls into one big bucket (e.g., `app/api/*`), it auto-splits by second-level directory (like `analytics`, `projects`, `sessions`)
119
+ - Token-safe AI: each group uses a compact `git diff --cached --numstat` summary (not full diffs) to generate the commit line
120
+
121
+ Usage:
122
+
123
+ ```sh
124
+ # Just run as usual; grouping triggers automatically when applicable
125
+ lazycommit
126
+
127
+ # Force grouping even for < 5 files
128
+ lazycommit --split
129
+ ```
130
+
112
131
  #### Handling large diffs
113
132
 
114
- For large commits with many files, lazycommit automatically stays within API limits:
133
+ For large commits with many files, lazycommit automatically stays within API limits and maintains clean history:
115
134
 
116
- - **Automatic detection**: Large diffs are detected
117
- - **Per-file splitting**: Diffs are split by file first
118
- - **Safe chunking**: Each file diff is chunked conservatively (default: 4000 tokens)
119
- - **Combination**: Results are combined into one concise message
135
+ - **Automatic detection**: Large diffs and many-file changes are detected
136
+ - **Logical grouping**: Files are grouped into conventional buckets; single huge buckets are auto-split by second-level directory (e.g., `app/api/<group>/...`)
137
+ - **Token-safe summaries**: Each group sends a small `--numstat` summary to AI instead of full diffs
138
+ - **Sequential commits**: In multi-commit mode, groups are committed one-by-one with their own messages
120
139
 
121
140
  ### Git hook
122
141
 
@@ -229,7 +248,14 @@ lazycommit config set proxy=
229
248
 
230
249
  #### model
231
250
 
232
- Default: `openai/gpt-oss-120b`
251
+ Default: `llama-3.1-8b-instant`
252
+
253
+ The Groq model to use for generating commit messages. Available models include:
254
+ - `llama-3.1-8b-instant` (default) - Fast, efficient for conventional commits
255
+ - `llama-3.1-70b-versatile` - More detailed but slower
256
+ - `llama-3.1-120b-versatile` - Most capable but slowest
257
+
258
+ For conventional commit generation, the 8B instant model provides the best balance of speed and quality.
233
259
 
234
260
  #### timeout
235
261
 
@@ -267,17 +293,6 @@ You can clear this option by setting it to an empty string:
267
293
  lazycommit config set type=
268
294
  ```
269
295
 
270
- #### chunk-size
271
-
272
- Default: `4000`
273
-
274
- The maximum number of tokens per chunk when processing large diffs. This helps avoid API limits and keeps requests fast:
275
-
276
- ```sh
277
- lazycommit config set chunk-size 4000
278
- ```
279
-
280
- **Note**: Must be between 1000-8000 tokens (Groq API limit).
281
296
 
282
297
  ## How it works
283
298
 
@@ -289,12 +304,13 @@ The tool uses Groq's fast inference API to provide quick and accurate commit mes
289
304
 
290
305
  For large commits that exceed API token limits, lazycommit automatically:
291
306
 
292
- 1. **Splits by file** to avoid oversized requests
293
- 2. **Chunks each file** into manageable pieces (default: 4000 tokens)
294
- 3. **Processes chunks** and combines results into a single message
295
- 4. **Falls back gracefully** to a high-level summary if needed
307
+ 1. **Detects large/many-file diffs** and switches to a scalable flow
308
+ 2. **Groups files** by conventional type/scope; if only one large bucket remains, **auto-splits by second-level directory** (e.g., `app/api/<group>/...`)
309
+ 3. **Generates messages per group** using compact `git diff --cached --numstat` summaries (not full diffs)
310
+ 4. **Commits sequentially** per group with clear, conventional messages
311
+ 5. When a single commit is requested, **uses compact summaries** to generate conventional messages efficiently
296
312
 
297
- This ensures you can commit large changes (like new features, refactoring, or initial project setup) without hitting API limits.
313
+ This ensures you can commit large changes (like new features, refactoring, or initial project setup) without hitting API limits, while keeping a clean history.
298
314
 
299
315
  ## Troubleshooting
300
316
 
@@ -307,17 +323,12 @@ If you get a 413 error, your diff is too large for the API. Try these solutions:
307
323
  lazycommit --exclude "dist/**" --exclude "node_modules/**" --exclude ".next/**"
308
324
  ```
309
325
 
310
- 2. **Reduce chunk size**:
311
- ```sh
312
- lazycommit config set chunk-size 4000
313
- ```
314
-
315
- 3. **Use a different model**:
326
+ 2. **Use a different model**:
316
327
  ```sh
317
328
  lazycommit config set model "llama-3.1-70b-versatile"
318
329
  ```
319
330
 
320
- 4. **Commit in smaller batches**:
331
+ 3. **Commit in smaller batches**:
321
332
  ```sh
322
333
  git add src/ # Stage only source files
323
334
  lazycommit
@@ -329,19 +340,23 @@ If you get a 413 error, your diff is too large for the API. Try these solutions:
329
340
 
330
341
  - Check your API key: `lazycommit config get GROQ_API_KEY`
331
342
  - Verify you have staged changes: `git status`
332
- - Try reducing chunk size or excluding large files
343
+ - Try excluding large files or using a different model
333
344
 
334
345
  ### Slow performance with large diffs
335
346
 
336
- - Reduce chunk size: `lazycommit config set chunk-size 4000`
347
+ - **Use the 8B instant model** (default): `lazycommit config set model "llama-3.1-8b-instant"`
337
348
  - Exclude unnecessary files: `lazycommit --exclude "*.log" --exclude "*.tmp"`
349
+ - Use automatic multi-commit mode to split large changes into logical groups
350
+ - Lower generate count: `lazycommit config set generate=1` (default)
351
+ - Reduce timeout: `lazycommit config set timeout=5000` for faster failures
338
352
 
339
353
  ## Why Groq?
340
354
 
341
- - **Fast**: Groq provides ultra-fast inference speeds
342
- - **Cost-effective**: More affordable than traditional AI APIs
343
- - **Open source models**: Uses leading open-source language models
344
- - **Reliable**: High uptime and consistent performance
355
+ - **Fast**: Groq provides ultra-fast inference speeds, especially with the 8B instant model
356
+ - **Cost-effective**: More affordable than traditional AI APIs
357
+ - **Open source models**: Uses leading open-source language models
358
+ - **Reliable**: High uptime and consistent performance
359
+ - **Optimized for commits**: The 8B instant model is perfectly sized for conventional commit generation
345
360
 
346
361
  ## Maintainers
347
362
 
package/dist/cli.mjs CHANGED
@@ -1,22 +1,16 @@
1
1
  #!/usr/bin/env node
2
- var ke=Object.defineProperty;var g=(e,t)=>ke(e,"name",{value:t,configurable:!0});import{command as X,cli as Ae}from"cleye";import{readFileSync as ve}from"fs";import{fileURLToPath as V,pathToFileURL as Ce}from"url";import z,{dirname as Pe,join as xe}from"path";import{execa as A}from"execa";import{dim as G,bgCyan as Z,black as L,green as _,red as T}from"kolorist";import{intro as ee,spinner as j,confirm as De,isCancel as te,outro as v,select as Me}from"@clack/prompts";import w from"fs/promises";import _e from"os";import se from"ini";import ne from"groq-sdk";var Te="1.0.10",Ie={version:Te};const q=class q extends Error{};g(q,"KnownError");let p=q;const Y=" ",I=g(e=>{e instanceof Error&&!(e instanceof p)&&(e.stack&&console.error(G(e.stack.split(`
2
+ var we=Object.defineProperty;var l=(t,e)=>we(t,"name",{value:e,configurable:!0});import{command as B,cli as $e}from"cleye";import{readFileSync as be}from"fs";import{fileURLToPath as q,pathToFileURL as ve}from"url";import G,{dirname as ke,join as Ae}from"path";import{execa as k}from"execa";import{dim as O,bgCyan as J,black as X,green as _,red as F}from"kolorist";import{intro as V,spinner as D,outro as x,confirm as Z,isCancel as Y,select as xe}from"@clack/prompts";import $ from"fs/promises";import Pe from"os";import ee from"ini";import te from"groq-sdk";var Ce="1.0.11",_e={version:Ce};const Q=class Q extends Error{};l(Q,"KnownError");let w=Q;const z=" ",M=l(t=>{t instanceof Error&&!(t instanceof w)&&(t.stack&&console.error(O(t.stack.split(`
3
3
  `).slice(1).join(`
4
4
  `))),console.error(`
5
- ${Y}${G(`lazycommit v${Ie.version}`)}`),console.error(`
6
- ${Y}Please open a Bug report with the information above:`),console.error(`${Y}https://github.com/KartikLabhshetwar/lazycommit/issues/new/choose`))},"handleCliError"),oe=g(async()=>{const{stdout:e,failed:t}=await A("git",["rev-parse","--show-toplevel"],{reject:!1});if(t)throw new p("The current directory must be a Git repository!");return e},"assertGitRepo"),O=g(e=>`:(exclude)${e}`,"excludeFromDiff"),N=["package-lock.json","pnpm-lock.yaml","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(O),ue=g(async e=>{const t=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await A("git",[...t,"--name-only",...N,...e?e.map(O):[]]);if(!s)return;const{stdout:n}=await A("git",[...t,...N,...e?e.map(O):[]]);return{files:s.split(`
7
- `),diff:n}},"getStagedDiff"),ae=g(e=>`Detected ${e.length.toLocaleString()} staged file${e.length>1?"s":""}`,"getDetectedMessage"),U=g(e=>Math.ceil(e.length/4),"estimateTokenCount"),ie=g((e,t=4e3)=>{if(U(e)<=t)return[e];const n=[],u=e.split(`
8
- `);let o="",r=0;for(const l of u){const f=U(l);r+f>t&&o.length>0?(n.push(o.trim()),o=l+`
9
- `,r=f):(o+=l+`
10
- `,r+=f)}return o.trim().length>0&&n.push(o.trim()),n},"chunkDiff"),re=g(async e=>{const t=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await A("git",[...t,"--name-only",...N,...e?e.map(O):[]]);if(!s)return null;const n=s.split(`
11
- `).filter(Boolean),u=await Promise.all(n.map(async o=>{try{const{stdout:r}=await A("git",[...t,"--numstat","--",o]),[l,f]=r.split(" ").slice(0,2).map(Number);return{file:o,additions:l||0,deletions:f||0,changes:(l||0)+(f||0)}}catch{return{file:o,additions:0,deletions:0,changes:0}}}));return{files:n,fileStats:u,totalChanges:u.reduce((o,r)=>o+r.changes,0)}},"getDiffSummary"),ce=g(e=>{const t=[];let s="";const n=e.split(`
12
- `);for(const u of n)u.startsWith("diff --git ")?(s.trim().length>0&&t.push(s.trim()),s=u+`
13
- `):s+=u+`
14
- `;return s.trim().length>0&&t.push(s.trim()),t},"splitDiffByFile"),Oe=g(async(e,t=20)=>{const s=await re(e);if(!s)return null;const{fileStats:n}=s,u=[...n].sort((h,i)=>i.changes-h.changes),o=u.slice(0,Math.max(1,t)),r=s.files.length,l=s.totalChanges,f=n.reduce((h,i)=>h+(i.additions||0),0),d=n.reduce((h,i)=>h+(i.deletions||0),0),a=[];a.push(`Files changed: ${r}`),a.push(`Additions: ${f}, Deletions: ${d}, Total changes: ${l}`),a.push("Top files by changes:");for(const h of o)a.push(`- ${h.file} (+${h.additions} / -${h.deletions}, ${h.changes} changes)`);return u.length>o.length&&a.push(`\u2026and ${u.length-o.length} more files`),a.join(`
15
- `)},"buildCompactSummary"),le=g(e=>w.lstat(e).then(()=>!0,()=>!1),"fileExists"),Ee=["","conventional"],{hasOwnProperty:Fe}=Object.prototype,me=g((e,t)=>Fe.call(e,t),"hasOwn"),y=g((e,t,s)=>{if(!t)throw new p(`Invalid config property ${e}: ${s}`)},"parseAssert"),E={GROQ_API_KEY(e){if(!e)throw new p("Please set your Groq API key via `lazycommit config set GROQ_API_KEY=<your token>`");return y("GROQ_API_KEY",e.startsWith("gsk_"),'Must start with "gsk_"'),e},locale(e){return e?(y("locale",e,"Cannot be empty"),y("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;y("generate",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return y("generate",t>0,"Must be greater than 0"),y("generate",t<=5,"Must be less or equal to 5"),t},type(e){return e?(y("type",Ee.includes(e),"Invalid commit type"),e):""},proxy(e){if(!(!e||e.length===0))return y("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;y("timeout",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return y("timeout",t>=500,"Must be greater than 500ms"),t},"max-length"(e){if(!e)return 50;y("max-length",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return y("max-length",t>=20,"Must be greater than 20 characters"),t},"chunk-size"(e){if(!e)return 4e3;y("chunk-size",/^\d+$/.test(e),"Must be an integer");const t=Number(e);return y("chunk-size",t>=1e3,"Must be at least 1000 tokens"),y("chunk-size",t<=8e3,"Must be at most 8000 tokens (Groq limit)"),t}},B=z.join(_e.homedir(),".lazycommit"),fe=g(async()=>{if(!await le(B))return Object.create(null);const t=await w.readFile(B,"utf8");return se.parse(t)},"readConfigFile"),H=g(async(e,t)=>{const s=await fe(),n={};for(const u of Object.keys(E)){const o=E[u],r=e?.[u]??s[u];if(t)try{n[u]=o(r)}catch{}else n[u]=o(r)}return n},"getConfig"),Se=g(async e=>{const t=await fe();for(const[s,n]of e){if(!me(E,s))throw new p(`Invalid config property: ${s}`);const u=E[s](n);t[s]=u}await w.writeFile(B,se.stringify(t),"utf8")},"setConfigs"),Re={"":"<commit message>",conventional:"<type>(<optional scope>): <commit message>"},ge={"":"",conventional:`Choose the most appropriate type from the following categories that best describes the git diff:
5
+ ${z}${O(`lazycommit v${_e.version}`)}`),console.error(`
6
+ ${z}Please open a Bug report with the information above:`),console.error(`${z}https://github.com/KartikLabhshetwar/lazycommit/issues/new/choose`))},"handleCliError"),se=l(async()=>{const{stdout:t,failed:e}=await k("git",["rev-parse","--show-toplevel"],{reject:!1});if(e)throw new w("The current directory must be a Git repository!");return t},"assertGitRepo"),T=l(t=>`:(exclude)${t}`,"excludeFromDiff"),L=["package-lock.json","pnpm-lock.yaml","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(T),oe=l(async t=>{const e=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await k("git",[...e,"--name-only",...L,...t?t.map(T):[]]);if(!s)return;const{stdout:i}=await k("git",[...e,...L,...t?t.map(T):[]]);return{files:s.split(`
7
+ `),diff:i}},"getStagedDiff"),ie=l(t=>`Detected ${t.length.toLocaleString()} staged file${t.length>1?"s":""}`,"getDetectedMessage"),ne=l(async t=>{const e=["diff","--cached","--diff-algorithm=minimal"],{stdout:s}=await k("git",[...e,"--name-only",...L,...t?t.map(T):[]]);if(!s)return null;const i=s.split(`
8
+ `).filter(Boolean),o=await Promise.all(i.map(async n=>{try{const{stdout:u}=await k("git",[...e,"--numstat","--",n]),[a,c]=u.split(" ").slice(0,2).map(Number);return{file:n,additions:a||0,deletions:c||0,changes:(a||0)+(c||0)}}catch{return{file:n,additions:0,deletions:0,changes:0}}}));return{files:i,fileStats:o,totalChanges:o.reduce((n,u)=>n+u.changes,0)}},"getDiffSummary"),ue=l(async(t,e=20)=>{const s=await ne(t);if(!s)return null;const{fileStats:i}=s,o=[...i].sort((p,m)=>m.changes-p.changes),n=o.slice(0,Math.max(1,e)),u=s.files.length,a=s.totalChanges,c=i.reduce((p,m)=>p+(m.additions||0),0),h=i.reduce((p,m)=>p+(m.deletions||0),0),y=[];y.push(`Files changed: ${u}`),y.push(`Additions: ${c}, Deletions: ${h}, Total changes: ${a}`),y.push("Top files by changes:");for(const p of n)y.push(`- ${p.file} (+${p.additions} / -${p.deletions}, ${p.changes} changes)`);return o.length>n.length&&y.push(`\u2026and ${o.length-n.length} more files`),y.join(`
9
+ `)},"buildCompactSummary"),ae=l(t=>$.lstat(t).then(()=>!0,()=>!1),"fileExists"),De=["","conventional"],{hasOwnProperty:We}=Object.prototype,ce=l((t,e)=>We.call(t,e),"hasOwn"),v=l((t,e,s)=>{if(!e)throw new w(`Invalid config property ${t}: ${s}`)},"parseAssert"),E={GROQ_API_KEY(t){if(!t)throw new w("Please set your Groq API key via `lazycommit config set GROQ_API_KEY=<your token>`");return v("GROQ_API_KEY",t.startsWith("gsk_"),'Must start with "gsk_"'),t},locale(t){return t?(v("locale",t,"Cannot be empty"),v("locale",/^[a-z-]+$/i.test(t),"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"),t):"en"},generate(t){if(!t)return 1;v("generate",/^\d+$/.test(t),"Must be an integer");const e=Number(t);return v("generate",e>0,"Must be greater than 0"),v("generate",e<=5,"Must be less or equal to 5"),e},type(t){return t?(v("type",De.includes(t),"Invalid commit type"),t):""},proxy(t){if(!(!t||t.length===0))return v("proxy",/^https?:\/\//.test(t),"Must be a valid URL"),t},model(t){return!t||t.length===0?"llama-3.1-8b-instant":t},timeout(t){if(!t)return 1e4;v("timeout",/^\d+$/.test(t),"Must be an integer");const e=Number(t);return v("timeout",e>=500,"Must be greater than 500ms"),e},"max-length"(t){if(!t)return 50;v("max-length",/^\d+$/.test(t),"Must be an integer");const e=Number(t);return v("max-length",e>=20,"Must be greater than 20 characters"),e}},N=G.join(Pe.homedir(),".lazycommit"),re=l(async()=>{if(!await ae(N))return Object.create(null);const e=await $.readFile(N,"utf8");return ee.parse(e)},"readConfigFile"),U=l(async(t,e)=>{const s=await re(),i={};for(const o of Object.keys(E)){const n=E[o],u=t?.[o]??s[o];if(e)try{i[o]=n(u)}catch{}else i[o]=n(u)}return i},"getConfig"),Se=l(async t=>{const e=await re();for(const[s,i]of t){if(!ce(E,s))throw new w(`Invalid config property: ${s}`);const o=E[s](i);e[s]=o}await $.writeFile(N,ee.stringify(e),"utf8")},"setConfigs"),Ie={"":"<commit message>",conventional:"<type>(<optional scope>): <commit message>"},le={"":"",conventional:`Choose the most appropriate type from the following categories that best describes the git diff:
16
10
 
17
11
  ${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)}
18
12
 
19
- IMPORTANT: Use the exact type name from the list above.`},F=g((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.
13
+ IMPORTANT: Use the exact type name from the list above.`},je=l((t,e,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.
20
14
 
21
15
  ## Instructions:
22
16
  1. Analyze the provided git diff carefully
@@ -35,13 +29,13 @@ IMPORTANT: Use the exact type name from the list above.`},F=g((e,t,s)=>`You are
35
29
  - For features, describe what functionality was added
36
30
  - For refactoring, mention what was improved (performance, readability, etc.)
37
31
 
38
- ## Language: ${e}
39
- ## Maximum length: ${t} characters
40
- ## Output format: ${Re[s]||"<commit message>"}
32
+ ## Language: ${t}
33
+ ## Maximum length: ${e} characters
34
+ ## Output format: ${Ie[s]||"<commit message>"}
41
35
 
42
- ${ge[s]?`
36
+ ${le[s]?`
43
37
  ## Commit Type Guidelines:
44
- ${ge[s]}`:""}
38
+ ${le[s]}`:""}
45
39
 
46
40
  ## Examples of good commit messages:
47
41
  - "feat: Add user authentication with JWT tokens"
@@ -58,30 +52,21 @@ ${ge[s]}`:""}
58
52
  - "WIP"
59
53
  - "Stuff"
60
54
 
61
- Remember: Your response will be used directly as the git commit message. Make it professional and informative.`,"generatePrompt"),S=g(async(e,t,s,n,u,o,r,l,f,d,a)=>{const h=new ne({apiKey:e,timeout:d});try{return f>1?{choices:(await Promise.all(Array.from({length:f},()=>h.chat.completions.create({model:t,messages:s,temperature:n,top_p:u,frequency_penalty:o,presence_penalty:r,max_tokens:l,n:1})))).flatMap(c=>c.choices)}:await h.chat.completions.create({model:t,messages:s,temperature:n,top_p:u,frequency_penalty:o,presence_penalty:r,max_tokens:l,n:1})}catch(i){if(i instanceof ne.APIError){let m=`Groq API Error: ${i.status} - ${i.name}`;throw i.message&&(m+=`
55
+ Remember: Your response will be used directly as the git commit message. Make it professional and informative.`,"generatePrompt"),Oe=l(async(t,e,s,i,o,n,u,a,c,h,y)=>{const p=new te({apiKey:t,timeout:h});try{return c>1?{choices:(await Promise.all(Array.from({length:c},()=>p.chat.completions.create({model:e,messages:s,temperature:i,top_p:o,frequency_penalty:n,presence_penalty:u,max_tokens:a,n:1})))).flatMap(d=>d.choices)}:await p.chat.completions.create({model:e,messages:s,temperature:i,top_p:o,frequency_penalty:n,presence_penalty:u,max_tokens:a,n:1})}catch(m){if(m instanceof te.APIError){let g=`Groq API Error: ${m.status} - ${m.name}`;throw m.message&&(g+=`
62
56
 
63
- ${i.message}`),i.status===500&&(m+=`
57
+ ${m.message}`),m.status===500&&(g+=`
64
58
 
65
- Check the API status: https://console.groq.com/status`),(i.status===413||i.message&&i.message.includes("rate_limit_exceeded"))&&(m+=`
59
+ Check the API status: https://console.groq.com/status`),(m.status===413||m.message&&m.message.includes("rate_limit_exceeded"))&&(g+=`
66
60
 
67
61
  \u{1F4A1} Tip: Your diff is too large. Try:
68
62
  1. Commit files in smaller batches
69
63
  2. Exclude large files with --exclude
70
64
  3. Use a different model with --model
71
- 4. Check if you have build artifacts staged (dist/, .next/, etc.)`),new p(m)}throw i.code==="ENOTFOUND"?new p(`Error connecting to ${i.hostname} (${i.syscall}). Are you connected to the internet?`):i}},"createChatCompletion"),P=g(e=>e.trim().replace(/[\n\r]/g,"").replace(/(\w)\.$/,"$1"),"sanitizeMessage"),he=g(e=>Array.from(new Set(e)),"deduplicateMessages"),ze=["feat:","fix:","docs:","style:","refactor:","perf:","test:","build:","ci:","chore:","revert:"],Q=g((e,t)=>{const s=e.replace(/\s+/g," ").trim(),n=s.match(/\b(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)\b\s*:?\s+[^.\n]+/i);let u=n?n[0]:s.split(/[.!?]/)[0];const o=u.toLowerCase();for(const r of ze){const l=r.slice(0,-1);if(o.startsWith(l+" ")&&!o.startsWith(r)){u=l+": "+u.slice(l.length+1);break}}return u=P(u),u?(u.length>t&&(u=u.slice(0,t)),u):null},"deriveMessageFromReasoning"),W=g(async(e,t,s,n,u,o,r,l,f)=>{try{const d=await S(e,t,[{role:"system",content:F(s,o,r)},{role:"user",content:n}],.7,1,0,0,Math.max(200,o*8),u,l,f),a=d.choices.map(i=>i.message?.content||"").map(i=>P(i)).filter(Boolean);if(a.length>0)return he(a);const h=d.choices.map(i=>i.message?.reasoning||"").filter(Boolean);for(const i of h){const m=Q(i,o);if(m)return[m]}return[]}catch(d){const a=d;throw a.code==="ENOTFOUND"?new p(`Error connecting to ${a.hostname} (${a.syscall}). Are you connected to the internet?`):a}},"generateCommitMessage"),de=g(async(e,t,s,n,u,o,r,l,f,d=6e3)=>{const h=ce(n).flatMap(c=>ie(c,d)),i=h.length>0?h:ie(n,d);if(i.length===1)try{return await W(e,t,s,n,u,o,r,l,f)}catch(c){throw new p(`Failed to generate commit message: ${c instanceof Error?c.message:"Unknown error"}`)}const m=[];for(let c=0;c<i.length;c++){const $=i[c],b=U($)+1200;let k=Math.max(200,o*8);b+k>7500&&(k=Math.max(200,7500-b));const x=`Analyze this git diff and propose a concise commit message limited to ${o} characters. Focus on the most significant intent of the change.
65
+ 4. Check if you have build artifacts staged (dist/, .next/, etc.)`),new w(g)}throw m.code==="ENOTFOUND"?new w(`Error connecting to ${m.hostname} (${m.syscall}). Are you connected to the internet?`):m}},"createChatCompletion"),me=l(t=>t.trim().replace(/[\n\r]/g,"").replace(/(\w)\.$/,"$1"),"sanitizeMessage"),Fe=l(t=>Array.from(new Set(t)),"deduplicateMessages"),Me=["feat:","fix:","docs:","style:","refactor:","perf:","test:","build:","ci:","chore:","revert:"],Te=l((t,e)=>{const s=t.replace(/\s+/g," ").trim(),i=s.match(/\b(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)\b\s*:?\s+[^.\n]+/i);let o=i?i[0]:s.split(/[.!?]/)[0];const n=o.toLowerCase();for(const u of Me){const a=u.slice(0,-1);if(n.startsWith(a+" ")&&!n.startsWith(u)){o=a+": "+o.slice(a.length+1);break}}return o=me(o),o?(o.length>e&&(o=o.slice(0,e)),o):null},"deriveMessageFromReasoning"),W=l(async(t,e,s,i,o,n,u,a,c)=>{const h=`This is a compact summary of staged changes. Generate a single, concise commit message within ${n} characters that reflects the overall intent.
72
66
 
73
- ${$}`;try{const C=await S(e,t,[{role:"system",content:F(s,o,r)},{role:"user",content:x}],.7,1,0,0,k,1,l,f),J=(C.choices||[]).map(D=>D.message?.content).filter(Boolean);if(J.length>0)m.push(P(J[0]));else{const D=C.choices.map(M=>M.message?.reasoning||"").filter(Boolean);if(D.length>0){const M=Q(D[0],o);M&&m.push(M)}}}catch(C){console.warn(`Failed to process chunk ${c+1}:`,C instanceof Error?C.message:"Unknown error")}}if(m.length===0){const c=ce(n).map(b=>(b.split(`
74
- `,1)[0]||"").split(" ")[2]?.replace("a/","")||"").filter(Boolean).slice(0,15),$=`Generate a single, concise commit message (<= ${o} chars) summarizing changes across these files:
75
- ${c.map(b=>`- ${b}`).join(`
76
- `)}`;try{const k=((await S(e,t,[{role:"system",content:F(s,o,r)},{role:"user",content:$}],.7,1,0,0,Math.max(200,o*8),1,l,f)).choices||[]).map(x=>x.message?.content).filter(Boolean);if(k.length>0)return[P(k[0])]}catch{}throw new p("Failed to generate commit messages for any chunks")}if(m.length>1){const c=`I have ${m.length} commit messages for different parts of a large change:
77
-
78
- ${m.map(($,b)=>`${b+1}. ${$}`).join(`
79
- `)}
80
-
81
- Please generate a single, comprehensive commit message that captures the overall changes.
82
- The message should be concise but cover the main aspects of all the changes.`;try{return await W(e,t,s,c,u,o,r,l,f)}catch{return m}}return m},"generateCommitMessageFromChunks"),Ge=g(async(e,t,s,n,u,o,r,l,f)=>{const d=`This is a compact summary of staged changes. Generate a single, concise commit message within ${o} characters that reflects the overall intent.
83
-
84
- ${n}`,a=await S(e,t,[{role:"system",content:F(s,o,r)},{role:"user",content:d}],.7,1,0,0,Math.max(200,o*8),u,l),h=(a.choices||[]).map(m=>m.message?.content||"").map(m=>P(m)).filter(Boolean);if(h.length>0)return he(h);const i=a.choices.map(m=>m.message?.reasoning||"").filter(Boolean);for(const m of i){const c=Q(m,o);if(c)return[c]}return[]},"generateCommitMessageFromSummary"),je=`\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
67
+ ${i}`,y=await Oe(t,e,[{role:"system",content:je(s,n,u)},{role:"user",content:h}],.7,1,0,0,Math.max(200,n*8),o,a),p=(y.choices||[]).map(g=>g.message?.content||"").map(g=>me(g)).filter(Boolean);if(p.length>0)return Fe(p);const m=y.choices.map(g=>g.message?.reasoning||"").filter(Boolean);for(const g of m){const d=Te(g,n);if(d)return[d]}return[]},"generateCommitMessageFromSummary"),Ee=l(t=>{const e=t.toLowerCase();return t.split("/"),e.endsWith(".md")||e.startsWith("docs/")||e.includes("/docs/")||e.includes("readme")||e.includes("changelog")||e.includes("license")||e.endsWith(".rst")||e.endsWith(".adoc")?{type:"docs",scope:void 0}:e.startsWith(".github/")||e.startsWith(".gitlab/")||(e.endsWith(".yml")||e.endsWith(".yaml"))&&(e.includes("workflow")||e.includes("pipeline")||e.includes("action")||e.includes("ci")||e.includes("deploy"))?{type:"ci",scope:void 0}:e.endsWith("package.json")||e.endsWith("pnpm-lock.yaml")||e.endsWith("yarn.lock")||e.endsWith("package-lock.json")||e.endsWith("tsconfig.json")||e.endsWith("jsconfig.json")||e.endsWith("vite.config.js")||e.endsWith("webpack.config.js")||e.endsWith("rollup.config.js")||e.endsWith("babel.config.js")||e.endsWith(".config.js")||e.endsWith(".config.ts")||e.endsWith("dockerfile")||e.endsWith("docker-compose.yml")||e.endsWith("makefile")||e.endsWith(".mk")||e.includes("webpack")||e.includes("rollup")||e.includes("vite")||e.includes("babel")||e.startsWith("config/")?{type:"build",scope:void 0}:e.includes("/test/")||e.includes("__tests__")||e.includes("/tests/")||e.match(/\.(test|spec)\.[jt]sx?$/)||e.includes(".test.")||e.includes(".spec.")||e.startsWith("test/")||e.startsWith("tests/")||e.includes("cypress/")||e.includes("jest/")||e.includes("vitest/")?{type:"test",scope:void 0}:e.includes("analytics")||e.includes("metrics")||e.includes("tracking")?{type:"feat",scope:"analytics"}:e.includes("auth")||e.includes("login")||e.includes("security")||e.includes("permission")||e.includes("role")?{type:"feat",scope:"auth"}:e.startsWith("app/api/")||e.includes("/api/")||e.includes("routes/")||e.includes("endpoint")||e.includes("handler")?{type:"feat",scope:"api"}:e.includes("database")||e.includes("/db/")||e.includes("migration")||e.includes("schema")||e.includes("model")||e.includes("entity")||e.endsWith(".sql")||e.includes("prisma/")?{type:"feat",scope:"db"}:e.includes("component")||e.includes("ui/")||e.includes("style")||e.endsWith(".css")||e.endsWith(".scss")||e.endsWith(".less")||e.endsWith(".styl")||e.includes("theme")?{type:"feat",scope:"ui"}:e.includes("util")||e.includes("helper")||e.includes("lib/")||e.includes("common/")||e.includes("shared/")?{type:"refactor",scope:"utils"}:e.startsWith("app/")||e.startsWith("src/app/")||e.includes("page")||e.includes("layout")||e.includes("route")?{type:"feat",scope:"app"}:e.startsWith("src/")?{type:"feat",scope:"core"}:{type:"chore",scope:void 0}},"classifyFile"),Re=l(t=>{const e=t.split("/");if(e.length>1)return e[0]},"topLevelScope"),Ge=l(t=>{const e=new Map;for(const o of t){const n=Ee(o),u=n.scope||Re(o),a=`${n.type}:${u||"general"}`,c=e.get(a)||{meta:{type:n.type,scope:u},files:[]};c.files.push(o),e.set(a,c)}const s=[];for(const{meta:o,files:n}of e.values()){const{type:u,scope:a}=o;let c="update files";u==="docs"?c="update documentation":u==="ci"?c="update CI workflows":u==="build"?c="update build/configuration":u==="test"?c="update tests":u==="feat"?c="add or update features":u==="chore"&&(c="maintenance updates");const h=a?`${c} in ${a}`:c;s.push({type:u,scope:a,title:h,files:n})}const i={feat:0,fix:1,refactor:2,perf:3,test:4,docs:5,build:6,ci:7,chore:8};return s.sort((o,n)=>(i[o.type]??99)-(i[n.type]??99)),s},"groupFiles"),Ye=l(t=>{const e=t.split("/");if(e.length>=4&&e[0]==="app"&&e[1]==="api")return e[2];if(e.length>=2)return e[1]},"getSecondLevelDir"),ze=l(async t=>{if(t.length===0)return"";const e=["diff","--cached","--numstat","--",...t],{stdout:s}=await k("git",e),i=[];let o=0,n=0;const u=s.split(`
68
+ `).filter(Boolean);for(const c of u){const[h,y,p]=c.split(" "),m=Number(h)||0,g=Number(y)||0;o+=m,n+=g,i.push(`- ${p} (+${m} / -${g})`)}return[`Files: ${t.length}, Additions: ${o}, Deletions: ${n}`,...i.slice(0,25)].join(`
69
+ `)},"summarizeFiles"),Le=`\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
85
70
  \u2502 \u2502
86
71
  \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
87
72
  \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
@@ -90,23 +75,28 @@ ${n}`,a=await S(e,t,[{role:"system",content:F(s,o,r)},{role:"user",content:d}],.
90
75
  \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
91
76
  \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
92
77
  \u2502 \u2502
93
- \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 Ye=g(async(e,t,s,n,u)=>(async()=>{console.log(je),console.log(),ee(Z(L(" lazycommit "))),await oe();const o=j();s&&await A("git",["add","--update"]),o.start("Detecting staged files");const r=await ue(t);if(!r)throw o.stop("Detecting staged files"),new p("No staged changes found. Stage your changes manually, or automatically stage all changes with the `--all` flag.");const l=await re(t),f=r.diff.length>5e4;f&&l?o.stop(`${ae(r.files)} (${l.totalChanges.toLocaleString()} changes):
94
- ${r.files.map(c=>` ${c}`).join(`
78
+ \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 Ne=l(async(t,e,s,i,o,n)=>(async()=>{console.log(Le),console.log(),V(J(X(" lazycommit "))),await se();const u=D();s&&await k("git",["add","--update"]),u.start("Detecting staged files");const a=await oe(e);if(!a)throw u.stop("Detecting staged files"),new w("No staged changes found. Stage your changes manually, or automatically stage all changes with the `--all` flag.");const c=await ne(e),h=a.diff.length>5e4,y=a.files.length>=5,p=c&&c.fileStats.some(r=>r.changes>500),m=h||y||p;if(m&&c){let r="Large diff detected";y?r="Many files detected":p&&(r="Large file changes detected"),u.stop(`${ie(a.files)} (${c.totalChanges.toLocaleString()} changes):
79
+ ${a.files.map(b=>` ${b}`).join(`
95
80
  `)}
96
81
 
97
- \u26A0\uFE0F Large diff detected - using chunked processing`):o.stop(`${ae(r.files)}:
98
- ${r.files.map(c=>` ${c}`).join(`
99
- `)}`);const{env:d}=process,a=await H({GROQ_API_KEY:d.GROQ_API_KEY,proxy:d.https_proxy||d.HTTPS_PROXY||d.http_proxy||d.HTTP_PROXY,generate:e?.toString(),type:n?.toString()}),h=j();h.start("The AI is analyzing your changes");let i;try{if(f){const c=await Oe(t,25);c?i=await Ge(a.GROQ_API_KEY,a.model,a.locale,c,a.generate,a["max-length"],a.type,a.timeout,a.proxy):i=await de(a.GROQ_API_KEY,a.model,a.locale,r.diff,a.generate,a["max-length"],a.type,a.timeout,a.proxy,a["chunk-size"])}else i=await de(a.GROQ_API_KEY,a.model,a.locale,r.diff,a.generate,a["max-length"],a.type,a.timeout,a.proxy,a["chunk-size"])}finally{h.stop("Changes analyzed")}if(i.length===0)throw new p("No commit messages were generated. Try again.");let m;if(i.length===1){[m]=i;const c=await De({message:`Use this commit message?
100
-
101
- ${m}
102
- `});if(!c||te(c)){v("Commit cancelled");return}}else{const c=await Me({message:`Pick a commit message to use: ${G("(Ctrl+c to exit)")}`,options:i.map($=>({label:$,value:$}))});if(te(c)){v("Commit cancelled");return}m=c}await A("git",["commit","-m",m,...u]),v(`${_("\u2714")} Successfully committed!`)})().catch(o=>{v(`${T("\u2716")} ${o.message}`),I(o),process.exit(1)}),"lazycommit");const[K,Ne]=process.argv.slice(2);var Ue=g(()=>(async()=>{if(!K)throw new p('Commit message file path is missing. This file should be called from the "prepare-commit-msg" git hook');if(Ne)return;const e=await ue();if(!e)return;ee(Z(L(" lazycommit ")));const{env:t}=process,s=await H({GROQ_API_KEY:t.GROQ_API_KEY,proxy:t.https_proxy||t.HTTPS_PROXY||t.http_proxy||t.HTTP_PROXY}),n=j();n.start("The AI is analyzing your changes");let u;try{u=await W(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 r=await w.readFile(K,"utf8")!=="",l=u.length>1;let f="";r&&(f=`# \u{1F916} AI generated commit${l?"s":""}
103
- `),l?(r&&(f+=`# Select one of the following messages by uncommeting:
104
- `),f+=`
105
- ${u.map(d=>`# ${d}`).join(`
106
- `)}`):(r&&(f+=`# Edit the message below and commit:
107
- `),f+=`
108
- ${u[0]}
109
- `),await w.appendFile(K,f),v(`${_("\u2714")} Saved commit message!`)})().catch(e=>{v(`${T("\u2716")} ${e.message}`),I(e),process.exit(1)}),"prepareCommitMessageHook"),Be=X({name:"config",parameters:["<mode>","<key=value...>"]},e=>{(async()=>{const{mode:t,keyValue:s}=e._;if(t==="get"){const n=await H({},!0);for(const u of s)me(n,u)&&console.log(`${u}=${n[u]}`);return}if(t==="set"){await Se(s.map(n=>n.split("=")));return}throw new p(`Invalid mode: ${t}`)})().catch(t=>{console.error(`${T("\u2716")} ${t.message}`),I(t),process.exit(1)})});const pe="prepare-commit-msg",ye=`.git/hooks/${pe}`,R=V(new URL("cli.mjs",import.meta.url)),He=process.argv[1].replace(/\\/g,"/").endsWith(`/${ye}`),we=process.platform==="win32",$e=`
82
+ \u{1F3AF} ${r} - using smart grouping for better commit history`)}else u.stop(`${ie(a.files)}:
83
+ ${a.files.map(r=>` ${r}`).join(`
84
+ `)}`);const{env:g}=process,d=await U({GROQ_API_KEY:g.GROQ_API_KEY,proxy:g.https_proxy||g.HTTPS_PROXY||g.http_proxy||g.HTTP_PROXY,generate:t?.toString(),type:i?.toString()});if(o||m){let r=Ge(a.files);if(r.length===1&&r[0].files.length>=5){const b=new Map;for(const A of r[0].files){const f=Ye(A)||"general";b.set(f,[...b.get(f)||[],A])}r=Array.from(b.entries()).map(([A,f])=>({type:r[0].type,scope:A,title:`update files in ${A}`,files:f}))}if(r.length===1)x(`${O("Only one group detected, falling back to single commit...")}`),o=!1;else{const b=D();b.start(`Generating commit messages for ${r.length} groups`);try{for(const f of r){const P=await ze(f.files),I=`Generate a single conventional commit message for this group of files. Follow commitlint (type(scope): subject). Keep <= ${d["max-length"]} chars.
85
+
86
+ ${P}`;try{const j=await W(d.GROQ_API_KEY,d.model,d.locale,I,1,d["max-length"],"conventional",d.timeout,d.proxy);if(j.length>0)f.message=j[0];else{const ye=f.scope?`(${f.scope})`:"";f.message=`${f.type}${ye}: ${f.title}`}}catch{const j=f.scope?`(${f.scope})`:"";f.message=`${f.type}${j}: ${f.title}`}}}finally{b.stop("Commit messages generated")}console.log(`
87
+ \u{1F4CB} Commit Plan:`),r.forEach((f,P)=>{console.log(`
88
+ ${P+1}. ${_(f.message)}`),console.log(` Files: ${f.files.join(", ")}`)});const A=await Z({message:`
89
+ Proceed with ${r.length} commits?`});if(!A||Y(A)){x("Commits cancelled");return}for(let f=0;f<r.length;f++){const P=r[f],I=D();I.start(`Creating commit ${f+1}/${r.length}: ${P.message}`);try{await k("git",["reset","HEAD","--"]),await k("git",["add",...P.files]),await k("git",["commit","-m",P.message,...n])}finally{I.stop(`Commit ${f+1} created`)}}x(`${_("\u2714")} Successfully created ${r.length} commits!`);return}}const K=D();K.start("The AI is analyzing your changes");let C;try{const r=await ue(e,25);if(r)C=await W(d.GROQ_API_KEY,d.model,d.locale,r,d.generate,d["max-length"],d.type,d.timeout,d.proxy);else{const A=`Generate a commit message for these files: ${a.files.join(", ")}`;C=await W(d.GROQ_API_KEY,d.model,d.locale,A,d.generate,d["max-length"],d.type,d.timeout,d.proxy)}}finally{K.stop("Changes analyzed")}if(C.length===0)throw new w("No commit messages were generated. Try again.");let S;if(C.length===1){[S]=C;const r=await Z({message:`Use this commit message?
90
+
91
+ ${S}
92
+ `});if(!r||Y(r)){x("Commit cancelled");return}}else{const r=await xe({message:`Pick a commit message to use: ${O("(Ctrl+c to exit)")}`,options:C.map(b=>({label:b,value:b}))});if(Y(r)){x("Commit cancelled");return}S=r}await k("git",["commit","-m",S,...n]),x(`${_("\u2714")} Successfully committed!`)})().catch(u=>{x(`${F("\u2716")} ${u.message}`),M(u),process.exit(1)}),"lazycommit");const[H,Ue]=process.argv.slice(2);var He=l(()=>(async()=>{if(!H)throw new w('Commit message file path is missing. This file should be called from the "prepare-commit-msg" git hook');if(Ue)return;const t=await oe();if(!t)return;V(J(X(" lazycommit ")));const{env:e}=process,s=await U({GROQ_API_KEY:e.GROQ_API_KEY,proxy:e.https_proxy||e.HTTPS_PROXY||e.http_proxy||e.HTTP_PROXY}),i=D();i.start("The AI is analyzing your changes");let o;try{const h=await ue();if(h)o=await W(s.GROQ_API_KEY,s.model,s.locale,h,s.generate,s["max-length"],s.type,s.timeout,s.proxy);else{const p=`Generate a commit message for these files: ${t.files.join(", ")}`;o=await W(s.GROQ_API_KEY,s.model,s.locale,p,s.generate,s["max-length"],s.type,s.timeout,s.proxy)}}finally{i.stop("Changes analyzed")}const u=await $.readFile(H,"utf8")!=="",a=o.length>1;let c="";u&&(c=`# \u{1F916} AI generated commit${a?"s":""}
93
+ `),a?(u&&(c+=`# Select one of the following messages by uncommeting:
94
+ `),c+=`
95
+ ${o.map(h=>`# ${h}`).join(`
96
+ `)}`):(u&&(c+=`# Edit the message below and commit:
97
+ `),c+=`
98
+ ${o[0]}
99
+ `),await $.appendFile(H,c),x(`${_("\u2714")} Saved commit message!`)})().catch(t=>{x(`${F("\u2716")} ${t.message}`),M(t),process.exit(1)}),"prepareCommitMessageHook"),Qe=B({name:"config",parameters:["<mode>","<key=value...>"]},t=>{(async()=>{const{mode:e,keyValue:s}=t._;if(e==="get"){const i=await U({},!0);for(const o of s)ce(i,o)&&console.log(`${o}=${i[o]}`);return}if(e==="set"){await Se(s.map(i=>i.split("=")));return}throw new w(`Invalid mode: ${e}`)})().catch(e=>{console.error(`${F("\u2716")} ${e.message}`),M(e),process.exit(1)})});const de="prepare-commit-msg",fe=`.git/hooks/${de}`,R=q(new URL("cli.mjs",import.meta.url)),Ke=process.argv[1].replace(/\\/g,"/").endsWith(`/${fe}`),pe=process.platform==="win32",ge=`
110
100
  #!/usr/bin/env node
111
- import(${JSON.stringify(Ce(R))})
112
- `.trim();var Qe=X({name:"hook",parameters:["<install/uninstall>"]},e=>{(async()=>{const t=await oe(),{installUninstall:s}=e._,n=z.join(t,ye),u=await le(n);if(s==="install"){if(u){if(await w.realpath(n).catch(()=>{})===R){console.warn("The hook is already installed");return}throw new p(`A different ${pe} hook seems to be installed. Please remove it before installing lazycommit.`)}await w.mkdir(z.dirname(n),{recursive:!0}),we?await w.writeFile(n,$e):(await w.symlink(R,n,"file"),await w.chmod(n,493)),console.log(`${_("\u2714")} Hook installed`);return}if(s==="uninstall"){if(!u){console.warn("Hook is not installed");return}if(we){if(await w.readFile(n,"utf8")!==$e){console.warn("Hook is not installed");return}}else if(await w.realpath(n)!==R){console.warn("Hook is not installed");return}await w.rm(n),console.log(`${_("\u2714")} Hook uninstalled`);return}throw new p(`Invalid mode: ${s}`)})().catch(t=>{console.error(`${T("\u2716")} ${t.message}`),I(t),process.exit(1)})});const We=V(import.meta.url),Ke=Pe(We),qe=JSON.parse(ve(xe(Ke,"../package.json"),"utf8")),{description:Je,version:Xe}=qe,be=process.argv.slice(2);Ae({name:"lazycommit",version:Xe,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:[Be,Qe],help:{description:Je},ignoreArgv:g(e=>e==="unknown-flag"||e==="argument","ignoreArgv")},e=>{He?Ue():Ye(e.flags.generate,e.flags.exclude,e.flags.all,e.flags.type,be)},be);
101
+ import(${JSON.stringify(ve(R))})
102
+ `.trim();var Be=B({name:"hook",parameters:["<install/uninstall>"]},t=>{(async()=>{const e=await se(),{installUninstall:s}=t._,i=G.join(e,fe),o=await ae(i);if(s==="install"){if(o){if(await $.realpath(i).catch(()=>{})===R){console.warn("The hook is already installed");return}throw new w(`A different ${de} hook seems to be installed. Please remove it before installing lazycommit.`)}await $.mkdir(G.dirname(i),{recursive:!0}),pe?await $.writeFile(i,ge):(await $.symlink(R,i,"file"),await $.chmod(i,493)),console.log(`${_("\u2714")} Hook installed`);return}if(s==="uninstall"){if(!o){console.warn("Hook is not installed");return}if(pe){if(await $.readFile(i,"utf8")!==ge){console.warn("Hook is not installed");return}}else if(await $.realpath(i)!==R){console.warn("Hook is not installed");return}await $.rm(i),console.log(`${_("\u2714")} Hook uninstalled`);return}throw new w(`Invalid mode: ${s}`)})().catch(e=>{console.error(`${F("\u2716")} ${e.message}`),M(e),process.exit(1)})});const qe=q(import.meta.url),Je=ke(qe),Xe=JSON.parse(be(Ae(Je,"../package.json"),"utf8")),{description:Ve,version:Ze}=Xe,he=process.argv.slice(2);$e({name:"lazycommit",version:Ze,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:[Qe,Be],help:{description:Ve},ignoreArgv:l(t=>t==="unknown-flag"||t==="argument","ignoreArgv")},t=>{Ke?He():Ne(t.flags.generate,t.flags.exclude,t.flags.all,t.flags.type,t.flags.split,he)},he);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazycommitt",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Writes your git commit messages for you with AI using Groq",
5
5
  "main": "index.js",
6
6
  "keywords": [