gac 2.3.0__tar.gz → 2.7.5__tar.gz

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.

Files changed (49) hide show
  1. {gac-2.3.0 → gac-2.7.5}/PKG-INFO +33 -13
  2. {gac-2.3.0 → gac-2.7.5}/README.md +32 -12
  3. {gac-2.3.0 → gac-2.7.5}/src/gac/__version__.py +1 -1
  4. {gac-2.3.0 → gac-2.7.5}/src/gac/ai.py +4 -2
  5. {gac-2.3.0 → gac-2.7.5}/src/gac/ai_utils.py +23 -2
  6. gac-2.7.5/src/gac/auth_cli.py +69 -0
  7. {gac-2.3.0 → gac-2.7.5}/src/gac/cli.py +14 -1
  8. {gac-2.3.0 → gac-2.7.5}/src/gac/config.py +3 -0
  9. {gac-2.3.0 → gac-2.7.5}/src/gac/constants.py +2 -0
  10. {gac-2.3.0 → gac-2.7.5}/src/gac/git.py +69 -12
  11. {gac-2.3.0 → gac-2.7.5}/src/gac/init_cli.py +194 -22
  12. gac-2.7.5/src/gac/language_cli.py +253 -0
  13. {gac-2.3.0 → gac-2.7.5}/src/gac/main.py +57 -8
  14. gac-2.7.5/src/gac/oauth/__init__.py +1 -0
  15. gac-2.7.5/src/gac/oauth/claude_code.py +397 -0
  16. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/__init__.py +2 -0
  17. gac-2.7.5/src/gac/providers/claude_code.py +102 -0
  18. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/custom_anthropic.py +1 -1
  19. {gac-2.3.0 → gac-2.7.5}/src/gac/utils.py +104 -3
  20. {gac-2.3.0 → gac-2.7.5}/src/gac/workflow_utils.py +5 -2
  21. gac-2.3.0/src/gac/language_cli.py +0 -82
  22. {gac-2.3.0 → gac-2.7.5}/.gitignore +0 -0
  23. {gac-2.3.0 → gac-2.7.5}/LICENSE +0 -0
  24. {gac-2.3.0 → gac-2.7.5}/pyproject.toml +0 -0
  25. {gac-2.3.0 → gac-2.7.5}/src/gac/__init__.py +0 -0
  26. {gac-2.3.0 → gac-2.7.5}/src/gac/config_cli.py +0 -0
  27. {gac-2.3.0 → gac-2.7.5}/src/gac/diff_cli.py +0 -0
  28. {gac-2.3.0 → gac-2.7.5}/src/gac/errors.py +0 -0
  29. {gac-2.3.0 → gac-2.7.5}/src/gac/preprocess.py +0 -0
  30. {gac-2.3.0 → gac-2.7.5}/src/gac/prompt.py +0 -0
  31. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/anthropic.py +0 -0
  32. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/cerebras.py +0 -0
  33. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/chutes.py +0 -0
  34. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/custom_openai.py +0 -0
  35. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/deepseek.py +0 -0
  36. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/fireworks.py +0 -0
  37. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/gemini.py +0 -0
  38. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/groq.py +0 -0
  39. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/lmstudio.py +0 -0
  40. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/minimax.py +0 -0
  41. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/mistral.py +0 -0
  42. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/ollama.py +0 -0
  43. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/openai.py +0 -0
  44. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/openrouter.py +0 -0
  45. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/streamlake.py +0 -0
  46. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/synthetic.py +0 -0
  47. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/together.py +0 -0
  48. {gac-2.3.0 → gac-2.7.5}/src/gac/providers/zai.py +0 -0
  49. {gac-2.3.0 → gac-2.7.5}/src/gac/security.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gac
3
- Version: 2.3.0
3
+ Version: 2.7.5
4
4
  Summary: LLM-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
@@ -41,6 +41,9 @@ Requires-Dist: twine; extra == 'dev'
41
41
  Description-Content-Type: text/markdown
42
42
 
43
43
  <!-- markdownlint-disable MD013 -->
44
+ <!-- markdownlint-disable MD033 MD036 -->
45
+
46
+ <div align="center">
44
47
 
45
48
  # 🚀 Git Auto Commit (gac)
46
49
 
@@ -50,9 +53,11 @@ Description-Content-Type: text/markdown
50
53
  [![codecov](https://codecov.io/gh/cellwebb/gac/branch/main/graph/badge.svg)](https://app.codecov.io/gh/cellwebb/gac)
51
54
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
52
55
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
53
- [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](docs/CONTRIBUTING.md)
56
+ [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](docs/en/CONTRIBUTING.md)
54
57
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
55
58
 
59
+ **English** | [简体中文](docs/zh-CN/README.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja/README.md) | [한국어](docs/ko/README.md) | [हिन्दी](docs/hi/README.md) | [Tiếng Việt](docs/vi/README.md) | [Français](docs/fr/README.md) | [Русский](docs/ru/README.md) | [Español](docs/es/README.md) | [Português](docs/pt/README.md) | [Norsk](docs/no/README.md) | [Svenska](docs/sv/README.md) | [Deutsch](docs/de/README.md) | [Nederlands](docs/nl/README.md) | [Italiano](docs/it/README.md)
60
+
56
61
  **LLM-powered commit messages that understand your code!**
57
62
 
58
63
  **Automate your commits!** Replace `git commit -m "..."` with `gac` for contextual, well-formatted commit messages generated by large language models!
@@ -67,12 +72,16 @@ Intelligent, contextual messages that explain the **why** behind your changes:
67
72
 
68
73
  ---
69
74
 
75
+ </div>
76
+
77
+ <!-- markdownlint-enable MD033 MD036 -->
78
+
70
79
  ## Quick Start
71
80
 
72
81
  ### Use gac without installing
73
82
 
74
83
  ```bash
75
- uvx gac init # Configure your LLM provider
84
+ uvx gac init # Configure your provider, model, and language
76
85
  uvx gac # Generate and commit with LLM
77
86
  ```
78
87
 
@@ -98,9 +107,10 @@ uv tool upgrade gac
98
107
 
99
108
  ### 🌐 **Supported Providers**
100
109
 
101
- - **Anthropic** • **Cerebras** • **Chutes.ai** • **DeepSeek** **Fireworks**
102
- - **Gemini** • **Groq** • **LM Studio** • **MiniMax** • **Mistral** **Ollama** • **OpenAI**
103
- - **OpenRouter** • **Streamlake** • **Synthetic.new** • **Together AI**
110
+ - **Anthropic** • **Cerebras** • **Chutes.ai** • **Claude Code**
111
+ - **DeepSeek** • **Fireworks** • **Gemini** • **Groq** • **LM Studio**
112
+ - **MiniMax** • **Mistral** • **Ollama** • **OpenAI** **OpenRouter**
113
+ - **Streamlake** • **Synthetic.new** • **Together AI**
104
114
  - **Z.AI** • **Z.AI Coding** • **Custom Endpoints (Anthropic/OpenAI)**
105
115
 
106
116
  ### 🧠 **Smart LLM Analysis**
@@ -121,7 +131,7 @@ uv tool upgrade gac
121
131
  - **25+ languages**: Generate commit messages in English, Chinese, Japanese, Korean, Spanish, French, German, and 20+ more languages
122
132
  - **Flexible translation**: Choose to keep conventional commit prefixes in English for tool compatibility, or fully translate them
123
133
  - **Multiple workflows**: Set a default language with `gac language`, or use `-l <language>` flag for one-time overrides
124
- - **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Arabic, and more
134
+ - **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Thai, and more
125
135
 
126
136
  ### 💻 **Developer Experience**
127
137
 
@@ -220,6 +230,8 @@ The edit feature (`e`) provides rich in-place terminal editing, allowing you to:
220
230
 
221
231
  Run `gac init` to configure your provider interactively, or set environment variables:
222
232
 
233
+ Need to change providers or models later without touching language settings? Use `gac model` for a streamlined flow that skips the language prompts.
234
+
223
235
  ```bash
224
236
  # Example configuration
225
237
  GAC_MODEL=anthropic:your-model-name
@@ -231,16 +243,24 @@ See `.gac.env.example` for all available options.
231
243
 
232
244
  **Want commit messages in another language?** Run `gac language` to select from 25+ languages including Español, Français, 日本語, and more.
233
245
 
234
- **Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
246
+ **Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
247
+
248
+ ---
249
+
250
+ ## Project Analytics
251
+
252
+ 📊 **[View live usage analytics and statistics →](https://clickpy.clickhouse.com/dashboard/gac)**
253
+
254
+ Track real-time installation metrics and package download statistics.
235
255
 
236
256
  ---
237
257
 
238
258
  ## Getting Help
239
259
 
240
- - **Full documentation**: [USAGE.md](USAGE.md) - Complete CLI reference
241
- - **Custom prompts**: [CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
242
- - **Troubleshooting**: [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) - Common issues and solutions
243
- - **Contributing**: [CONTRIBUTING.md](docs/CONTRIBUTING.md) - Development setup and guidelines
260
+ - **Full documentation**: [docs/USAGE.md](docs/en/USAGE.md) - Complete CLI reference
261
+ - **Custom prompts**: [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
262
+ - **Troubleshooting**: [docs/TROUBLESHOOTING.md](docs/en/TROUBLESHOOTING.md) - Common issues and solutions
263
+ - **Contributing**: [docs/CONTRIBUTING.md](docs/en/CONTRIBUTING.md) - Development setup and guidelines
244
264
 
245
265
  ---
246
266
 
@@ -250,7 +270,7 @@ See `.gac.env.example` for all available options.
250
270
 
251
271
  Made with ❤️ for developers who want better commit messages
252
272
 
253
- [⭐ Star us on GitHub](https://github.com/cellwebb/gac) • [🐛 Report issues](https://github.com/cellwebb/gac/issues) • [📖 Full docs](USAGE.md)
273
+ [⭐ Star us on GitHub](https://github.com/cellwebb/gac) • [🐛 Report issues](https://github.com/cellwebb/gac/issues) • [📖 Full docs](docs/en/USAGE.md)
254
274
 
255
275
  </div>
256
276
 
@@ -1,4 +1,7 @@
1
1
  <!-- markdownlint-disable MD013 -->
2
+ <!-- markdownlint-disable MD033 MD036 -->
3
+
4
+ <div align="center">
2
5
 
3
6
  # 🚀 Git Auto Commit (gac)
4
7
 
@@ -8,9 +11,11 @@
8
11
  [![codecov](https://codecov.io/gh/cellwebb/gac/branch/main/graph/badge.svg)](https://app.codecov.io/gh/cellwebb/gac)
9
12
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
10
13
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
11
- [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](docs/CONTRIBUTING.md)
14
+ [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](docs/en/CONTRIBUTING.md)
12
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
16
 
17
+ **English** | [简体中文](docs/zh-CN/README.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja/README.md) | [한국어](docs/ko/README.md) | [हिन्दी](docs/hi/README.md) | [Tiếng Việt](docs/vi/README.md) | [Français](docs/fr/README.md) | [Русский](docs/ru/README.md) | [Español](docs/es/README.md) | [Português](docs/pt/README.md) | [Norsk](docs/no/README.md) | [Svenska](docs/sv/README.md) | [Deutsch](docs/de/README.md) | [Nederlands](docs/nl/README.md) | [Italiano](docs/it/README.md)
18
+
14
19
  **LLM-powered commit messages that understand your code!**
15
20
 
16
21
  **Automate your commits!** Replace `git commit -m "..."` with `gac` for contextual, well-formatted commit messages generated by large language models!
@@ -25,12 +30,16 @@ Intelligent, contextual messages that explain the **why** behind your changes:
25
30
 
26
31
  ---
27
32
 
33
+ </div>
34
+
35
+ <!-- markdownlint-enable MD033 MD036 -->
36
+
28
37
  ## Quick Start
29
38
 
30
39
  ### Use gac without installing
31
40
 
32
41
  ```bash
33
- uvx gac init # Configure your LLM provider
42
+ uvx gac init # Configure your provider, model, and language
34
43
  uvx gac # Generate and commit with LLM
35
44
  ```
36
45
 
@@ -56,9 +65,10 @@ uv tool upgrade gac
56
65
 
57
66
  ### 🌐 **Supported Providers**
58
67
 
59
- - **Anthropic** • **Cerebras** • **Chutes.ai** • **DeepSeek** **Fireworks**
60
- - **Gemini** • **Groq** • **LM Studio** • **MiniMax** • **Mistral** **Ollama** • **OpenAI**
61
- - **OpenRouter** • **Streamlake** • **Synthetic.new** • **Together AI**
68
+ - **Anthropic** • **Cerebras** • **Chutes.ai** • **Claude Code**
69
+ - **DeepSeek** • **Fireworks** • **Gemini** • **Groq** • **LM Studio**
70
+ - **MiniMax** • **Mistral** • **Ollama** • **OpenAI** **OpenRouter**
71
+ - **Streamlake** • **Synthetic.new** • **Together AI**
62
72
  - **Z.AI** • **Z.AI Coding** • **Custom Endpoints (Anthropic/OpenAI)**
63
73
 
64
74
  ### 🧠 **Smart LLM Analysis**
@@ -79,7 +89,7 @@ uv tool upgrade gac
79
89
  - **25+ languages**: Generate commit messages in English, Chinese, Japanese, Korean, Spanish, French, German, and 20+ more languages
80
90
  - **Flexible translation**: Choose to keep conventional commit prefixes in English for tool compatibility, or fully translate them
81
91
  - **Multiple workflows**: Set a default language with `gac language`, or use `-l <language>` flag for one-time overrides
82
- - **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Arabic, and more
92
+ - **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Thai, and more
83
93
 
84
94
  ### 💻 **Developer Experience**
85
95
 
@@ -178,6 +188,8 @@ The edit feature (`e`) provides rich in-place terminal editing, allowing you to:
178
188
 
179
189
  Run `gac init` to configure your provider interactively, or set environment variables:
180
190
 
191
+ Need to change providers or models later without touching language settings? Use `gac model` for a streamlined flow that skips the language prompts.
192
+
181
193
  ```bash
182
194
  # Example configuration
183
195
  GAC_MODEL=anthropic:your-model-name
@@ -189,16 +201,24 @@ See `.gac.env.example` for all available options.
189
201
 
190
202
  **Want commit messages in another language?** Run `gac language` to select from 25+ languages including Español, Français, 日本語, and more.
191
203
 
192
- **Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
204
+ **Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
205
+
206
+ ---
207
+
208
+ ## Project Analytics
209
+
210
+ 📊 **[View live usage analytics and statistics →](https://clickpy.clickhouse.com/dashboard/gac)**
211
+
212
+ Track real-time installation metrics and package download statistics.
193
213
 
194
214
  ---
195
215
 
196
216
  ## Getting Help
197
217
 
198
- - **Full documentation**: [USAGE.md](USAGE.md) - Complete CLI reference
199
- - **Custom prompts**: [CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
200
- - **Troubleshooting**: [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) - Common issues and solutions
201
- - **Contributing**: [CONTRIBUTING.md](docs/CONTRIBUTING.md) - Development setup and guidelines
218
+ - **Full documentation**: [docs/USAGE.md](docs/en/USAGE.md) - Complete CLI reference
219
+ - **Custom prompts**: [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
220
+ - **Troubleshooting**: [docs/TROUBLESHOOTING.md](docs/en/TROUBLESHOOTING.md) - Common issues and solutions
221
+ - **Contributing**: [docs/CONTRIBUTING.md](docs/en/CONTRIBUTING.md) - Development setup and guidelines
202
222
 
203
223
  ---
204
224
 
@@ -208,7 +228,7 @@ See `.gac.env.example` for all available options.
208
228
 
209
229
  Made with ❤️ for developers who want better commit messages
210
230
 
211
- [⭐ Star us on GitHub](https://github.com/cellwebb/gac) • [🐛 Report issues](https://github.com/cellwebb/gac/issues) • [📖 Full docs](USAGE.md)
231
+ [⭐ Star us on GitHub](https://github.com/cellwebb/gac) • [🐛 Report issues](https://github.com/cellwebb/gac/issues) • [📖 Full docs](docs/en/USAGE.md)
212
232
 
213
233
  </div>
214
234
 
@@ -1,3 +1,3 @@
1
1
  """Version information for gac package."""
2
2
 
3
- __version__ = "2.3.0"
3
+ __version__ = "2.7.5"
@@ -13,6 +13,7 @@ from gac.providers import (
13
13
  call_anthropic_api,
14
14
  call_cerebras_api,
15
15
  call_chutes_api,
16
+ call_claude_code_api,
16
17
  call_custom_anthropic_api,
17
18
  call_custom_openai_api,
18
19
  call_deepseek_api,
@@ -48,7 +49,7 @@ def generate_commit_message(
48
49
  """Generate a commit message using direct API calls to AI providers.
49
50
 
50
51
  Args:
51
- model: The model to use in provider:model_name format (e.g., 'anthropic:claude-3-5-haiku-latest')
52
+ model: The model to use in provider:model_name format (e.g., 'anthropic:claude-haiku-4-5')
52
53
  prompt: Either a string prompt (for backward compatibility) or tuple of (system_prompt, user_prompt)
53
54
  temperature: Controls randomness (0.0-1.0), lower values are more deterministic
54
55
  max_tokens: Maximum tokens in the response
@@ -62,7 +63,7 @@ def generate_commit_message(
62
63
  AIError: If generation fails after max_retries attempts
63
64
 
64
65
  Example:
65
- >>> model = "anthropic:claude-3-5-haiku-latest"
66
+ >>> model = "anthropic:claude-haiku-4-5"
66
67
  >>> system_prompt, user_prompt = build_prompt("On branch main", "diff --git a/README.md b/README.md")
67
68
  >>> generate_commit_message(model, (system_prompt, user_prompt))
68
69
  'docs: Update README with installation instructions'
@@ -88,6 +89,7 @@ def generate_commit_message(
88
89
  provider_funcs = {
89
90
  "anthropic": call_anthropic_api,
90
91
  "cerebras": call_cerebras_api,
92
+ "claude-code": call_claude_code_api,
91
93
  "chutes": call_chutes_api,
92
94
  "custom-anthropic": call_custom_anthropic_api,
93
95
  "custom-openai": call_custom_openai_api,
@@ -4,6 +4,7 @@ This module provides utility functions that support the AI provider implementati
4
4
  """
5
5
 
6
6
  import logging
7
+ import os
7
8
  import time
8
9
  from functools import lru_cache
9
10
  from typing import Any
@@ -11,23 +12,34 @@ from typing import Any
11
12
  import tiktoken
12
13
  from halo import Halo
13
14
 
14
- from gac.constants import Utility
15
+ from gac.constants import EnvDefaults, Utility
15
16
  from gac.errors import AIError
16
17
 
17
18
  logger = logging.getLogger(__name__)
18
19
 
19
20
 
21
+ @lru_cache(maxsize=1)
22
+ def _should_skip_tiktoken_counting() -> bool:
23
+ """Return True when token counting should avoid tiktoken calls entirely."""
24
+ value = os.getenv("GAC_NO_TIKTOKEN", str(EnvDefaults.NO_TIKTOKEN))
25
+ return value.lower() in ("true", "1", "yes", "on")
26
+
27
+
20
28
  def count_tokens(content: str | list[dict[str, str]] | dict[str, Any], model: str) -> int:
21
29
  """Count tokens in content using the model's tokenizer."""
22
30
  text = extract_text_content(content)
23
31
  if not text:
24
32
  return 0
25
33
 
34
+ if _should_skip_tiktoken_counting():
35
+ return len(text) // 4
36
+
26
37
  try:
27
38
  encoding = get_encoding(model)
28
39
  return len(encoding.encode(text))
29
40
  except Exception as e:
30
41
  logger.error(f"Error counting tokens: {e}")
42
+ # Fallback to rough estimation (4 chars per token on average)
31
43
  return len(text) // 4
32
44
 
33
45
 
@@ -45,10 +57,18 @@ def extract_text_content(content: str | list[dict[str, str]] | dict[str, Any]) -
45
57
  @lru_cache(maxsize=1)
46
58
  def get_encoding(model: str) -> tiktoken.Encoding:
47
59
  """Get the appropriate encoding for a given model."""
48
- model_name = model.split(":")[-1] if ":" in model else model
60
+ provider, model_name = model.split(":", 1) if ":" in model else (None, model)
61
+
62
+ if provider != "openai":
63
+ return tiktoken.get_encoding(Utility.DEFAULT_ENCODING)
64
+
49
65
  try:
50
66
  return tiktoken.encoding_for_model(model_name)
51
67
  except KeyError:
68
+ # Fall back to default encoding if model not found
69
+ return tiktoken.get_encoding(Utility.DEFAULT_ENCODING)
70
+ except Exception:
71
+ # If there are any network/SSL issues, fall back to default encoding
52
72
  return tiktoken.get_encoding(Utility.DEFAULT_ENCODING)
53
73
 
54
74
 
@@ -98,6 +118,7 @@ def generate_with_retries(
98
118
  "anthropic",
99
119
  "cerebras",
100
120
  "chutes",
121
+ "claude-code",
101
122
  "deepseek",
102
123
  "fireworks",
103
124
  "gemini",
@@ -0,0 +1,69 @@
1
+ """CLI for authenticating Claude Code OAuth tokens.
2
+
3
+ Provides a command to authenticate and re-authenticate Claude Code subscriptions.
4
+ """
5
+
6
+ import logging
7
+
8
+ import click
9
+
10
+ from gac.oauth.claude_code import authenticate_and_save, load_stored_token
11
+ from gac.utils import setup_logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @click.command()
17
+ @click.option(
18
+ "--quiet",
19
+ "-q",
20
+ is_flag=True,
21
+ help="Suppress non-error output",
22
+ )
23
+ @click.option(
24
+ "--log-level",
25
+ default="INFO",
26
+ type=click.Choice(["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], case_sensitive=False),
27
+ help="Set log level (default: INFO)",
28
+ )
29
+ def auth(quiet: bool = False, log_level: str = "INFO") -> None:
30
+ """Authenticate Claude Code OAuth token.
31
+
32
+ This command allows you to authenticate or re-authenticate your
33
+ Claude Code OAuth token when it expires or you want to refresh it.
34
+ It opens a browser window for the OAuth flow and saves the token
35
+ to ~/.gac.env.
36
+
37
+ The token is used by the Claude Code provider to access your
38
+ Claude Code subscription instead of requiring an Anthropic API key.
39
+ """
40
+ # Setup logging
41
+ if quiet:
42
+ effective_log_level = "ERROR"
43
+ else:
44
+ effective_log_level = log_level
45
+ setup_logging(effective_log_level)
46
+
47
+ # Check if there's an existing token
48
+ existing_token = load_stored_token()
49
+ if existing_token and not quiet:
50
+ click.echo("✓ Found existing Claude Code access token.")
51
+ click.echo()
52
+
53
+ if not quiet:
54
+ click.echo("🔐 Starting Claude Code OAuth authentication...")
55
+ click.echo(" Your browser will open automatically")
56
+ click.echo(" (Waiting up to 3 minutes for callback)")
57
+ click.echo()
58
+
59
+ # Perform OAuth authentication
60
+ success = authenticate_and_save(quiet=quiet)
61
+
62
+ if success:
63
+ if not quiet:
64
+ click.echo("✅ Claude Code authentication completed successfully!")
65
+ click.echo(" Your new token has been saved and is ready to use.")
66
+ else:
67
+ click.echo("❌ Claude Code authentication failed.")
68
+ click.echo(" Please try again or check your network connection.")
69
+ raise click.ClickException("Claude Code authentication failed")
@@ -11,12 +11,14 @@ import sys
11
11
  import click
12
12
 
13
13
  from gac import __version__
14
+ from gac.auth_cli import auth as auth_cli
14
15
  from gac.config import load_config
15
16
  from gac.config_cli import config as config_cli
16
17
  from gac.constants import Languages, Logging
17
18
  from gac.diff_cli import diff as diff_cli
18
19
  from gac.errors import handle_error
19
20
  from gac.init_cli import init as init_cli
21
+ from gac.init_cli import model as model_cli
20
22
  from gac.language_cli import language as language_cli
21
23
  from gac.main import main
22
24
  from gac.utils import setup_logging
@@ -65,6 +67,12 @@ logger = logging.getLogger(__name__)
65
67
  # Advanced options
66
68
  @click.option("--no-verify", is_flag=True, help="Skip pre-commit and lefthook hooks when committing")
67
69
  @click.option("--skip-secret-scan", is_flag=True, help="Skip security scan for secrets in staged changes")
70
+ @click.option(
71
+ "--hook-timeout",
72
+ type=int,
73
+ default=0,
74
+ help="Timeout for pre-commit and lefthook hooks in seconds (0 to use configuration)",
75
+ )
68
76
  # Other options
69
77
  @click.option("--version", is_flag=True, help="Show the version of the Git Auto Commit (gac) tool")
70
78
  @click.pass_context
@@ -87,6 +95,7 @@ def cli(
87
95
  verbose: bool = False,
88
96
  no_verify: bool = False,
89
97
  skip_secret_scan: bool = False,
98
+ hook_timeout: int = 0,
90
99
  ) -> None:
91
100
  """Git Auto Commit - Generate commit messages with AI."""
92
101
  if ctx.invoked_subcommand is None:
@@ -125,6 +134,7 @@ def cli(
125
134
  no_verify=no_verify,
126
135
  skip_secret_scan=skip_secret_scan or bool(config.get("skip_secret_scan", False)),
127
136
  language=resolved_language,
137
+ hook_timeout=hook_timeout if hook_timeout > 0 else int(config.get("hook_timeout", 120) or 120),
128
138
  )
129
139
  except Exception as e:
130
140
  handle_error(e, exit_program=True)
@@ -150,13 +160,16 @@ def cli(
150
160
  "verbose": verbose,
151
161
  "no_verify": no_verify,
152
162
  "skip_secret_scan": skip_secret_scan,
163
+ "hook_timeout": hook_timeout,
153
164
  }
154
165
 
155
166
 
167
+ cli.add_command(auth_cli)
156
168
  cli.add_command(config_cli)
169
+ cli.add_command(diff_cli)
157
170
  cli.add_command(init_cli)
158
171
  cli.add_command(language_cli)
159
- cli.add_command(diff_cli)
172
+ cli.add_command(model_cli)
160
173
 
161
174
 
162
175
  @click.command(context_settings=language_cli.context_settings)
@@ -37,10 +37,13 @@ def load_config() -> dict[str, str | int | float | bool | None]:
37
37
  in ("true", "1", "yes", "on"),
38
38
  "skip_secret_scan": os.getenv("GAC_SKIP_SECRET_SCAN", str(EnvDefaults.SKIP_SECRET_SCAN)).lower()
39
39
  in ("true", "1", "yes", "on"),
40
+ "no_tiktoken": os.getenv("GAC_NO_TIKTOKEN", str(EnvDefaults.NO_TIKTOKEN)).lower() in ("true", "1", "yes", "on"),
40
41
  "verbose": os.getenv("GAC_VERBOSE", str(EnvDefaults.VERBOSE)).lower() in ("true", "1", "yes", "on"),
41
42
  "system_prompt_path": os.getenv("GAC_SYSTEM_PROMPT_PATH"),
42
43
  "language": os.getenv("GAC_LANGUAGE"),
43
44
  "translate_prefixes": os.getenv("GAC_TRANSLATE_PREFIXES", "false").lower() in ("true", "1", "yes", "on"),
45
+ "rtl_confirmed": os.getenv("GAC_RTL_CONFIRMED", "false").lower() in ("true", "1", "yes", "on"),
46
+ "hook_timeout": int(os.getenv("GAC_HOOK_TIMEOUT", EnvDefaults.HOOK_TIMEOUT)),
44
47
  }
45
48
 
46
49
  return config
@@ -25,6 +25,8 @@ class EnvDefaults:
25
25
  ALWAYS_INCLUDE_SCOPE: bool = False
26
26
  SKIP_SECRET_SCAN: bool = False
27
27
  VERBOSE: bool = False
28
+ NO_TIKTOKEN: bool = False
29
+ HOOK_TIMEOUT: int = 120 # Timeout for pre-commit and lefthook hooks in seconds
28
30
 
29
31
 
30
32
  class Logging:
@@ -14,6 +14,63 @@ from gac.utils import run_subprocess
14
14
  logger = logging.getLogger(__name__)
15
15
 
16
16
 
17
+ def run_subprocess_with_encoding_fallback(
18
+ command: list[str], silent: bool = False, timeout: int = 60
19
+ ) -> subprocess.CompletedProcess:
20
+ """Run subprocess with encoding fallback, returning full CompletedProcess object.
21
+
22
+ This is used for cases where we need both stdout and stderr separately,
23
+ like pre-commit and lefthook hook execution.
24
+
25
+ Args:
26
+ command: List of command arguments
27
+ silent: If True, suppress debug logging
28
+ timeout: Command timeout in seconds
29
+
30
+ Returns:
31
+ CompletedProcess object with stdout, stderr, and returncode
32
+ """
33
+ from gac.utils import get_safe_encodings
34
+
35
+ encodings = get_safe_encodings()
36
+ last_exception: Exception | None = None
37
+
38
+ for encoding in encodings:
39
+ try:
40
+ if not silent:
41
+ logger.debug(f"Running command: {' '.join(command)} (encoding: {encoding})")
42
+
43
+ result = subprocess.run(
44
+ command,
45
+ capture_output=True,
46
+ text=True,
47
+ check=False,
48
+ timeout=timeout,
49
+ encoding=encoding,
50
+ errors="replace",
51
+ )
52
+ return result
53
+ except UnicodeError as e:
54
+ last_exception = e
55
+ if not silent:
56
+ logger.debug(f"Failed to decode with {encoding}: {e}")
57
+ continue
58
+ except subprocess.TimeoutExpired:
59
+ raise
60
+ except Exception as e:
61
+ if not silent:
62
+ logger.debug(f"Command error: {e}")
63
+ # Try next encoding for non-timeout errors
64
+ last_exception = e
65
+ continue
66
+
67
+ # If we get here, all encodings failed
68
+ if last_exception:
69
+ raise subprocess.CalledProcessError(1, command, "", f"Encoding error: {last_exception}") from last_exception
70
+ else:
71
+ raise subprocess.CalledProcessError(1, command, "", "All encoding attempts failed")
72
+
73
+
17
74
  def run_git_command(args: list[str], silent: bool = False, timeout: int = 30) -> str:
18
75
  """Run a git command and return the output."""
19
76
  command = ["git"] + args
@@ -132,23 +189,23 @@ def get_diff(staged: bool = True, color: bool = True, commit1: str | None = None
132
189
 
133
190
  def get_repo_root() -> str:
134
191
  """Get absolute path of repository root."""
135
- result = subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
136
- return result.decode().strip()
192
+ result = run_git_command(["rev-parse", "--show-toplevel"])
193
+ return result
137
194
 
138
195
 
139
196
  def get_current_branch() -> str:
140
197
  """Get name of current git branch."""
141
- result = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
142
- return result.decode().strip()
198
+ result = run_git_command(["rev-parse", "--abbrev-ref", "HEAD"])
199
+ return result
143
200
 
144
201
 
145
202
  def get_commit_hash() -> str:
146
203
  """Get SHA-1 hash of current commit."""
147
- result = subprocess.check_output(["git", "rev-parse", "HEAD"])
148
- return result.decode().strip()
204
+ result = run_git_command(["rev-parse", "HEAD"])
205
+ return result
149
206
 
150
207
 
151
- def run_pre_commit_hooks() -> bool:
208
+ def run_pre_commit_hooks(hook_timeout: int = 120) -> bool:
152
209
  """Run pre-commit hooks if they exist.
153
210
 
154
211
  Returns:
@@ -168,9 +225,9 @@ def run_pre_commit_hooks() -> bool:
168
225
  return True
169
226
 
170
227
  # Run pre-commit hooks on staged files
171
- logger.info("Running pre-commit hooks...")
228
+ logger.info(f"Running pre-commit hooks with {hook_timeout}s timeout...")
172
229
  # Run pre-commit and capture both stdout and stderr
173
- result = subprocess.run(["pre-commit", "run"], capture_output=True, text=True, check=False)
230
+ result = run_subprocess_with_encoding_fallback(["pre-commit", "run"], timeout=hook_timeout)
174
231
 
175
232
  if result.returncode == 0:
176
233
  # All hooks passed
@@ -195,7 +252,7 @@ def run_pre_commit_hooks() -> bool:
195
252
  return True
196
253
 
197
254
 
198
- def run_lefthook_hooks() -> bool:
255
+ def run_lefthook_hooks(hook_timeout: int = 120) -> bool:
199
256
  """Run Lefthook hooks if they exist.
200
257
 
201
258
  Returns:
@@ -218,9 +275,9 @@ def run_lefthook_hooks() -> bool:
218
275
  return True
219
276
 
220
277
  # Run lefthook hooks on staged files
221
- logger.info("Running Lefthook hooks...")
278
+ logger.info(f"Running Lefthook hooks with {hook_timeout}s timeout...")
222
279
  # Run lefthook and capture both stdout and stderr
223
- result = subprocess.run(["lefthook", "run", "pre-commit"], capture_output=True, text=True, check=False)
280
+ result = run_subprocess_with_encoding_fallback(["lefthook", "run", "pre-commit"], timeout=hook_timeout)
224
281
 
225
282
  if result.returncode == 0:
226
283
  # All hooks passed