gac 1.6.0__py3-none-any.whl → 1.8.0__py3-none-any.whl

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.

Potentially problematic release.


This version of gac might be problematic. Click here for more details.

gac/__version__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information for gac package."""
2
2
 
3
- __version__ = "1.6.0"
3
+ __version__ = "1.8.0"
gac/ai.py CHANGED
@@ -12,6 +12,7 @@ from gac.errors import AIError
12
12
  from gac.providers import (
13
13
  call_anthropic_api,
14
14
  call_cerebras_api,
15
+ call_chutes_api,
15
16
  call_gemini_api,
16
17
  call_groq_api,
17
18
  call_lmstudio_api,
@@ -69,6 +70,7 @@ def generate_commit_message(
69
70
  provider_funcs = {
70
71
  "anthropic": call_anthropic_api,
71
72
  "cerebras": call_cerebras_api,
73
+ "chutes": call_chutes_api,
72
74
  "gemini": call_gemini_api,
73
75
  "groq": call_groq_api,
74
76
  "lmstudio": call_lmstudio_api,
gac/ai_utils.py CHANGED
@@ -98,7 +98,7 @@ def generate_with_retries(
98
98
  "cerebras",
99
99
  "gemini",
100
100
  "groq",
101
- "lmstudio",
101
+ "lm-studio",
102
102
  "ollama",
103
103
  "openai",
104
104
  "openrouter",
gac/cli.py CHANGED
@@ -53,7 +53,7 @@ logger = logging.getLogger(__name__)
53
53
  help=f"Set log level (default: {config['log_level']})",
54
54
  )
55
55
  # Advanced options
56
- @click.option("--no-verify", is_flag=True, help="Skip pre-commit hooks when committing")
56
+ @click.option("--no-verify", is_flag=True, help="Skip pre-commit and lefthook hooks when committing")
57
57
  @click.option("--skip-secret-scan", is_flag=True, help="Skip security scan for secrets in staged changes")
58
58
  # Other options
59
59
  @click.option("--version", is_flag=True, help="Show the version of the Git Auto Commit (gac) tool")
gac/git.py CHANGED
@@ -153,6 +153,56 @@ def run_pre_commit_hooks() -> bool:
153
153
  return True
154
154
 
155
155
 
156
+ def run_lefthook_hooks() -> bool:
157
+ """Run Lefthook hooks if they exist.
158
+
159
+ Returns:
160
+ True if Lefthook hooks passed or don't exist, False if they failed.
161
+ """
162
+ # Check for common Lefthook configuration files
163
+ lefthook_configs = [".lefthook.yml", "lefthook.yml", ".lefthook.yaml", "lefthook.yaml"]
164
+ config_exists = any(os.path.exists(config) for config in lefthook_configs)
165
+
166
+ if not config_exists:
167
+ logger.debug("No Lefthook configuration found, skipping Lefthook hooks")
168
+ return True
169
+
170
+ # Check if lefthook is installed and configured
171
+ try:
172
+ # First check if lefthook is installed
173
+ result = run_subprocess(["lefthook", "--version"], silent=True, raise_on_error=False)
174
+ if not result:
175
+ logger.debug("Lefthook not installed, skipping hooks")
176
+ return True
177
+
178
+ # Run lefthook hooks on staged files
179
+ logger.info("Running Lefthook hooks...")
180
+ # Run lefthook and capture both stdout and stderr
181
+ result = subprocess.run(["lefthook", "run", "pre-commit"], capture_output=True, text=True, check=False)
182
+
183
+ if result.returncode == 0:
184
+ # All hooks passed
185
+ return True
186
+ else:
187
+ # Lefthook hooks failed - show the output
188
+ output = result.stdout if result.stdout else ""
189
+ error = result.stderr if result.stderr else ""
190
+
191
+ # Combine outputs (lefthook usually outputs to stdout)
192
+ full_output = output + ("\n" + error if error else "")
193
+
194
+ if full_output.strip():
195
+ # Show which hooks failed and why
196
+ logger.error(f"Lefthook hooks failed:\n{full_output}")
197
+ else:
198
+ logger.error(f"Lefthook hooks failed with exit code {result.returncode}")
199
+ return False
200
+ except Exception as e:
201
+ logger.debug(f"Error running Lefthook: {e}")
202
+ # If lefthook isn't available, don't block the commit
203
+ return True
204
+
205
+
156
206
  def push_changes() -> bool:
157
207
  """Push committed changes to the remote repository."""
158
208
  remote_exists = run_git_command(["remote"])
gac/init_cli.py CHANGED
@@ -32,8 +32,9 @@ def init() -> None:
32
32
  click.echo(f"Created $HOME/.gac.env at {GAC_ENV_PATH}.")
33
33
 
34
34
  providers = [
35
- ("Anthropic", "claude-3-5-haiku-latest"),
35
+ ("Anthropic", "claude-haiku-4-5"),
36
36
  ("Cerebras", "qwen-3-coder-480b"),
37
+ ("Chutes.ai", "zai-org/GLM-4.6-FP8"),
37
38
  ("Gemini", "gemini-2.5-flash"),
38
39
  ("Groq", "meta-llama/llama-4-maverick-17b-128e-instruct"),
39
40
  ("LM Studio", "gemma3"),
gac/main.py CHANGED
@@ -19,6 +19,7 @@ from gac.git import (
19
19
  get_staged_files,
20
20
  push_changes,
21
21
  run_git_command,
22
+ run_lefthook_hooks,
22
23
  run_pre_commit_hooks,
23
24
  )
24
25
  from gac.preprocess import preprocess_diff
@@ -80,11 +81,18 @@ def main(
80
81
  )
81
82
  sys.exit(0)
82
83
 
83
- # Run pre-commit hooks before doing expensive operations
84
+ # Run pre-commit and lefthook hooks before doing expensive operations
84
85
  if not no_verify and not dry_run:
86
+ # Run lefthook hooks
87
+ if not run_lefthook_hooks():
88
+ console.print("[red]Lefthook hooks failed. Please fix the issues and try again.[/red]")
89
+ console.print("[yellow]You can use --no-verify to skip pre-commit and lefthook hooks.[/yellow]")
90
+ sys.exit(1)
91
+
92
+ # Run pre-commit hooks
85
93
  if not run_pre_commit_hooks():
86
94
  console.print("[red]Pre-commit hooks failed. Please fix the issues and try again.[/red]")
87
- console.print("[yellow]You can use --no-verify to skip pre-commit hooks.[/yellow]")
95
+ console.print("[yellow]You can use --no-verify to skip pre-commit and lefthook hooks.[/yellow]")
88
96
  sys.exit(1)
89
97
 
90
98
  status = run_git_command(["status"])
gac/providers/__init__.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from .anthropic import call_anthropic_api
4
4
  from .cerebras import call_cerebras_api
5
+ from .chutes import call_chutes_api
5
6
  from .gemini import call_gemini_api
6
7
  from .groq import call_groq_api
7
8
  from .lmstudio import call_lmstudio_api
@@ -15,6 +16,7 @@ from .zai import call_zai_api, call_zai_coding_api
15
16
  __all__ = [
16
17
  "call_anthropic_api",
17
18
  "call_cerebras_api",
19
+ "call_chutes_api",
18
20
  "call_gemini_api",
19
21
  "call_groq_api",
20
22
  "call_lmstudio_api",
@@ -0,0 +1,71 @@
1
+ """Chutes.ai API provider for gac."""
2
+
3
+ import os
4
+
5
+ import httpx
6
+
7
+ from gac.errors import AIError
8
+
9
+
10
+ def call_chutes_api(model: str, messages: list[dict], temperature: float, max_tokens: int) -> str:
11
+ """Call Chutes.ai API directly.
12
+
13
+ Chutes.ai provides an OpenAI-compatible API for serverless, decentralized AI compute.
14
+
15
+ Args:
16
+ model: The model to use (e.g., 'deepseek-ai/DeepSeek-V3-0324')
17
+ messages: List of message dictionaries with 'role' and 'content' keys
18
+ temperature: Controls randomness (0.0-1.0)
19
+ max_tokens: Maximum tokens in the response
20
+
21
+ Returns:
22
+ The generated commit message
23
+
24
+ Raises:
25
+ AIError: If authentication fails, API errors occur, or response is invalid
26
+ """
27
+ api_key = os.getenv("CHUTES_API_KEY")
28
+ if not api_key:
29
+ raise AIError.authentication_error("CHUTES_API_KEY environment variable not set")
30
+
31
+ base_url = os.getenv("CHUTES_BASE_URL", "https://llm.chutes.ai")
32
+ url = f"{base_url}/v1/chat/completions"
33
+
34
+ headers = {
35
+ "Content-Type": "application/json",
36
+ "Authorization": f"Bearer {api_key}",
37
+ }
38
+
39
+ data = {
40
+ "model": model,
41
+ "messages": messages,
42
+ "temperature": temperature,
43
+ "max_tokens": max_tokens,
44
+ }
45
+
46
+ try:
47
+ response = httpx.post(url, headers=headers, json=data, timeout=120)
48
+ response.raise_for_status()
49
+ response_data = response.json()
50
+ content = response_data["choices"][0]["message"]["content"]
51
+ if content is None:
52
+ raise AIError.model_error("Chutes.ai API returned null content")
53
+ if content == "":
54
+ raise AIError.model_error("Chutes.ai API returned empty content")
55
+ return content
56
+ except httpx.HTTPStatusError as e:
57
+ status_code = e.response.status_code
58
+ error_text = e.response.text
59
+
60
+ if status_code == 429:
61
+ raise AIError.rate_limit_error(f"Chutes.ai API rate limit exceeded: {error_text}") from e
62
+ elif status_code in (502, 503):
63
+ raise AIError.connection_error(f"Chutes.ai API service unavailable: {status_code} - {error_text}") from e
64
+ else:
65
+ raise AIError.model_error(f"Chutes.ai API error: {status_code} - {error_text}") from e
66
+ except httpx.ConnectError as e:
67
+ raise AIError.connection_error(f"Chutes.ai API connection error: {str(e)}") from e
68
+ except httpx.TimeoutException as e:
69
+ raise AIError.timeout_error(f"Chutes.ai API request timed out: {str(e)}") from e
70
+ except Exception as e:
71
+ raise AIError.model_error(f"Error calling Chutes.ai API: {str(e)}") from e
@@ -9,6 +9,10 @@ from gac.errors import AIError
9
9
 
10
10
  def call_synthetic_api(model: str, messages: list[dict], temperature: float, max_tokens: int) -> str:
11
11
  """Call Synthetic API directly."""
12
+ # Handle model names without hf: prefix
13
+ if not model.startswith("hf:"):
14
+ model = f"hf:{model}"
15
+
12
16
  api_key = os.getenv("SYNTHETIC_API_KEY") or os.getenv("SYN_API_KEY")
13
17
  if not api_key:
14
18
  raise AIError.authentication_error("SYNTHETIC_API_KEY or SYN_API_KEY not found in environment variables")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gac
3
- Version: 1.6.0
3
+ Version: 1.8.0
4
4
  Summary: AI-powered Git commit message generator with multi-provider support
5
5
  Project-URL: Homepage, https://github.com/cellwebb/gac
6
6
  Project-URL: Documentation, https://github.com/cellwebb/gac#readme
@@ -24,6 +24,7 @@ Requires-Python: >=3.10
24
24
  Requires-Dist: anthropic>=0.68.0
25
25
  Requires-Dist: click>=8.3.0
26
26
  Requires-Dist: halo
27
+ Requires-Dist: httpcore>=1.0.9
27
28
  Requires-Dist: httpx>=0.28.0
28
29
  Requires-Dist: pydantic>=2.12.0
29
30
  Requires-Dist: python-dotenv>=1.1.1
@@ -33,8 +34,8 @@ Requires-Dist: sumy
33
34
  Requires-Dist: tiktoken>=0.12.0
34
35
  Provides-Extra: dev
35
36
  Requires-Dist: build; extra == 'dev'
36
- Requires-Dist: bump-my-version; extra == 'dev'
37
37
  Requires-Dist: codecov; extra == 'dev'
38
+ Requires-Dist: pre-commit; extra == 'dev'
38
39
  Requires-Dist: pytest; extra == 'dev'
39
40
  Requires-Dist: pytest-cov; extra == 'dev'
40
41
  Requires-Dist: ruff; extra == 'dev'
@@ -57,7 +58,7 @@ Description-Content-Type: text/markdown
57
58
 
58
59
  - **LLM-Powered Commit Messages:** Automatically generates clear, concise, and context-aware commit messages using large language models.
59
60
  - **Deep Contextual Analysis:** Understands your code by analyzing staged changes, repository structure, and recent commit history to provide highly relevant suggestions.
60
- - **Multi-Provider & Model Support:** Flexibly works with leading AI providers (Anthropic, Cerebras, Gemini, Groq, OpenAI, OpenRouter, Streamlake/Vanchin, Z.AI) and local providers (LM Studio, Ollama), easily configured through an interactive setup or environment variables.
61
+ - **Multi-Provider & Model Support:** Flexibly works with leading AI providers (Anthropic, Cerebras, Chutes.ai, Gemini, Groq, OpenAI, OpenRouter, Streamlake/Vanchin, Synthetic.new, & Z.AI) and local providers (LM Studio & Ollama), easily configured through an interactive setup or environment variables.
61
62
  - **Seamless Git Workflow:** Integrates smoothly into your existing Git routine as a simple drop-in replacement for `git commit`.
62
63
  - **Extensive Customization:** Tailor commit messages to your needs with a rich set of flags, including one-liners (`-o`), AI hints (`-h`), scope inference (`-s`), and specific model selection (`-m`).
63
64
  - **Streamlined Workflow Commands:** Boost your productivity with convenient options to stage all changes (`-a`), auto-confirm commits (`-y`), and push to your remote repository (`-p`) in a single step.
@@ -69,12 +70,6 @@ Description-Content-Type: text/markdown
69
70
 
70
71
  gac analyzes your staged changes to generate high-quality commit messages with the help of large language models. The tool uses a sophisticated prompt architecture that separates system instructions from user data, enabling better AI understanding and more consistent results.
71
72
 
72
- ### Technical Architecture
73
-
74
- - **Dual-Prompt System**: GAC uses a separated prompt architecture where system instructions (role definition, conventions, examples) are sent as system messages, while git data (diffs, status) are sent as user messages. This follows AI best practices for improved model performance.
75
- - **Smart Context Analysis**: The tool examines your repository structure, recent commit history, and README files to understand the broader context of your changes.
76
- - **Intelligent Diff Processing**: Large diffs are automatically preprocessed to focus on the most important changes while staying within token limits.
77
-
78
73
  ## How to Use
79
74
 
80
75
  ```sh
@@ -1,23 +1,24 @@
1
1
  gac/__init__.py,sha256=z9yGInqtycFIT3g1ca24r-A3699hKVaRqGUI79wsmMc,415
2
- gac/__version__.py,sha256=Roc4mrxthdXl5rPRtyhrtsoqIlxTWC0IrZ5NqP60A_s,66
3
- gac/ai.py,sha256=zl9-rmESKTHP2KNstS4sBqMGvGNC-KYcEsPPCpshaUE,3456
4
- gac/ai_utils.py,sha256=6iSz2q8MohNf2M3CNmF_9eNRDuACLB2kuh_Q9bRrvdE,7252
5
- gac/cli.py,sha256=nvz6l-wctfo3SMpC-zqtXyHMg8rtdzxw9cllbVMXJ0w,4872
2
+ gac/__version__.py,sha256=ZrEUwsZomybTNkOGMfS08i073_U6EUzf2X5DfW6ia3g,66
3
+ gac/ai.py,sha256=T8o7ZenWDB94ILMvt5hp8mp_J6BbMJ3h5Y_0y2kRrX8,3512
4
+ gac/ai_utils.py,sha256=kz_ROoJjFZGPXxiyy225MuvMv9DikiqJBXOqKVlLVlc,7253
5
+ gac/cli.py,sha256=nvJW_FmS0xA8Vs7x-JdIu4mW9Ze7IelYP8bnZYVFB4E,4885
6
6
  gac/config.py,sha256=N62phuLUyVj54eLDiDL6VN8-2_Zt6yB5zsnimFavU3I,1630
7
7
  gac/config_cli.py,sha256=v9nFHZO1RvK9fzHyuUS6SG-BCLHMsdOMDwWamBhVVh4,1608
8
8
  gac/constants.py,sha256=hGzmLGhVDB2KPIqwtl6tHMNuSwHj-2P1RK0cGm4pyNA,4962
9
9
  gac/diff_cli.py,sha256=wnVQ9OFGnM0d2Pj9WVjWbo0jxqIuRHVAwmb8wU9Pa3E,5676
10
10
  gac/errors.py,sha256=ysDIVRCd0YQVTOW3Q6YzdolxCdtkoQCAFf3_jrqbjUY,7916
11
- gac/git.py,sha256=MS2m4fv8h4mau1djFG1aje9NXTmkGsjPO9w18LqNGX0,6031
12
- gac/init_cli.py,sha256=_0FAdJCgrdV61bUVmQm5ykX5TlCsOUcyKKlRY6zav5A,4638
13
- gac/main.py,sha256=5yL5bgTzfps3b3Nx9Obie8dyxa0VYlQ3Vot5Uw-dmr4,15289
11
+ gac/git.py,sha256=_Co25XA1Nku03h50C_HiEf4ugEz6rK5j_IYi-rr5gco,8005
12
+ gac/init_cli.py,sha256=LEw26g-tb6GZdN3DaUuLx3OFvZPKPOIFKnqgDRe6Y8s,4677
13
+ gac/main.py,sha256=ot1DEbJHsZ0SzdZUuRXIm9pz1xbm4hxARzUjwytGdfc,15670
14
14
  gac/preprocess.py,sha256=krrLPHsccYMdn_YAtUrppBJIoRgevxGWusDwhE40LEo,15366
15
15
  gac/prompt.py,sha256=K6r9q2cAlyPu1fud6-jJsZ4zeweEo3yt6_WeYv8a_SQ,17087
16
16
  gac/security.py,sha256=M1MZm6BLOeKl6rH_-UdXsSKol39FnA5fIP3YP394yZE,9898
17
17
  gac/utils.py,sha256=W3ladtmsH01MNLdckQYTzYrYbTGEdzCKI36he9C-y_E,3945
18
- gac/providers/__init__.py,sha256=MB0c9utV3grWKRX4mtPLRwxC138m2W_N4up12pZMZi0,817
18
+ gac/providers/__init__.py,sha256=ejIM5vvmfTp7vfJSNeQQPIEJusOkKTUZpUE7OeWBc9Y,876
19
19
  gac/providers/anthropic.py,sha256=VK5d1s1PmBNDwh_x7illQ2CIZIHNIYU28btVfizwQPs,2036
20
20
  gac/providers/cerebras.py,sha256=Ik8lhlsliGJVkgDgqlThfpra9tqbdYQZkaC4eNxRd9w,1648
21
+ gac/providers/chutes.py,sha256=cclJOLuGVIiiaF-9Bs1kH6SSOhEmduGB2zZ86KIaXKw,2617
21
22
  gac/providers/gemini.py,sha256=GZQz6Y9fd5-xk-U4pXn9bXLeBowxDXOYDyWyrtjFurM,2909
22
23
  gac/providers/groq.py,sha256=9v2fAjDa_iRNHFptiUBN8Vt7ZDKkW_JOmIBeYvycD1M,2806
23
24
  gac/providers/lmstudio.py,sha256=R82-f0tWdFfGQxLT6o3Q2tfvYguF7ESUg9DEUHNyrDk,2146
@@ -25,10 +26,10 @@ gac/providers/ollama.py,sha256=hPkagbhEiAoH9RTET4EQe9-lTL0YmMRCbQ5dVbRQw6Q,2095
25
26
  gac/providers/openai.py,sha256=iHVD6bHf57W-QmW7u1Ee5vOpev7XZ-K75NcolLfebOk,1630
26
27
  gac/providers/openrouter.py,sha256=H3ce8JcRUYq1I30lOjGESdX7jfoPkW3mKAYnc2aYfBw,2204
27
28
  gac/providers/streamlake.py,sha256=KAA2ZnpuEI5imzvdWVWUhEBHSP0BMnprKXte6CbwBWY,2047
28
- gac/providers/synthetic.py,sha256=Te_bj5Q7foLnTch89-hCGwt8V9O_6BzJZ0eMESArpLM,1744
29
+ gac/providers/synthetic.py,sha256=sRMIJTS9LpcXd9A7qp_ZjZxdqtTKRn9fl1W4YwJZP4c,1855
29
30
  gac/providers/zai.py,sha256=kywhhrCfPBu0rElZyb-iENxQxxpVGykvePuL4xrXlaU,2739
30
- gac-1.6.0.dist-info/METADATA,sha256=WXIPxP8Pcx9yVu4c-0BgRYy8DTY1pLFLXq-17t9Fx_A,9821
31
- gac-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
32
- gac-1.6.0.dist-info/entry_points.txt,sha256=tdjN-XMmcWfL92swuRAjT62bFLOAwk9bTMRLGP5Z4aI,36
33
- gac-1.6.0.dist-info/licenses/LICENSE,sha256=vOab37NouL1PNs5BswnPayrMCqaN2sqLfMQfqPDrpZg,1103
34
- gac-1.6.0.dist-info/RECORD,,
31
+ gac-1.8.0.dist-info/METADATA,sha256=4z_AS3NKai5FD1xvY8hYH_lWVGioLT7tpv93y_dhIAY,9245
32
+ gac-1.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ gac-1.8.0.dist-info/entry_points.txt,sha256=tdjN-XMmcWfL92swuRAjT62bFLOAwk9bTMRLGP5Z4aI,36
34
+ gac-1.8.0.dist-info/licenses/LICENSE,sha256=vOab37NouL1PNs5BswnPayrMCqaN2sqLfMQfqPDrpZg,1103
35
+ gac-1.8.0.dist-info/RECORD,,
File without changes