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.
- {gac-2.3.0 → gac-2.7.5}/PKG-INFO +33 -13
- {gac-2.3.0 → gac-2.7.5}/README.md +32 -12
- {gac-2.3.0 → gac-2.7.5}/src/gac/__version__.py +1 -1
- {gac-2.3.0 → gac-2.7.5}/src/gac/ai.py +4 -2
- {gac-2.3.0 → gac-2.7.5}/src/gac/ai_utils.py +23 -2
- gac-2.7.5/src/gac/auth_cli.py +69 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/cli.py +14 -1
- {gac-2.3.0 → gac-2.7.5}/src/gac/config.py +3 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/constants.py +2 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/git.py +69 -12
- {gac-2.3.0 → gac-2.7.5}/src/gac/init_cli.py +194 -22
- gac-2.7.5/src/gac/language_cli.py +253 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/main.py +57 -8
- gac-2.7.5/src/gac/oauth/__init__.py +1 -0
- gac-2.7.5/src/gac/oauth/claude_code.py +397 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/__init__.py +2 -0
- gac-2.7.5/src/gac/providers/claude_code.py +102 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/custom_anthropic.py +1 -1
- {gac-2.3.0 → gac-2.7.5}/src/gac/utils.py +104 -3
- {gac-2.3.0 → gac-2.7.5}/src/gac/workflow_utils.py +5 -2
- gac-2.3.0/src/gac/language_cli.py +0 -82
- {gac-2.3.0 → gac-2.7.5}/.gitignore +0 -0
- {gac-2.3.0 → gac-2.7.5}/LICENSE +0 -0
- {gac-2.3.0 → gac-2.7.5}/pyproject.toml +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/__init__.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/config_cli.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/diff_cli.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/errors.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/preprocess.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/prompt.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/anthropic.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/cerebras.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/chutes.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/custom_openai.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/deepseek.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/fireworks.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/gemini.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/groq.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/lmstudio.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/minimax.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/mistral.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/ollama.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/openai.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/openrouter.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/streamlake.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/synthetic.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/together.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/providers/zai.py +0 -0
- {gac-2.3.0 → gac-2.7.5}/src/gac/security.py +0 -0
{gac-2.3.0 → gac-2.7.5}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version: 2.
|
|
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
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
51
54
|
[](https://github.com/astral-sh/ruff)
|
|
52
55
|
[](https://mypy-lang.org/)
|
|
53
|
-
[](docs/CONTRIBUTING.md)
|
|
56
|
+
[](docs/en/CONTRIBUTING.md)
|
|
54
57
|
[](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
|
|
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** • **
|
|
102
|
-
- **
|
|
103
|
-
- **
|
|
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,
|
|
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
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
9
12
|
[](https://github.com/astral-sh/ruff)
|
|
10
13
|
[](https://mypy-lang.org/)
|
|
11
|
-
[](docs/CONTRIBUTING.md)
|
|
14
|
+
[](docs/en/CONTRIBUTING.md)
|
|
12
15
|
[](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
|
|
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** • **
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
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,
|
|
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
|
|
|
@@ -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-
|
|
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-
|
|
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(":")
|
|
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(
|
|
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
|
|
@@ -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 =
|
|
136
|
-
return result
|
|
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 =
|
|
142
|
-
return result
|
|
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 =
|
|
148
|
-
return result
|
|
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 =
|
|
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 =
|
|
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
|