gac 2.0.0__tar.gz → 2.2.0__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.0.0 → gac-2.2.0}/PKG-INFO +29 -10
- {gac-2.0.0 → gac-2.2.0}/README.md +27 -9
- {gac-2.0.0 → gac-2.2.0}/pyproject.toml +1 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/__version__.py +1 -1
- {gac-2.0.0 → gac-2.2.0}/src/gac/ai.py +2 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/ai_utils.py +1 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/cli.py +10 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/constants.py +32 -1
- {gac-2.0.0 → gac-2.2.0}/src/gac/init_cli.py +6 -32
- {gac-2.0.0 → gac-2.2.0}/src/gac/language_cli.py +4 -33
- {gac-2.0.0 → gac-2.2.0}/src/gac/main.py +14 -1
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/__init__.py +2 -0
- gac-2.2.0/src/gac/providers/mistral.py +38 -0
- gac-2.2.0/src/gac/utils.py +270 -0
- gac-2.0.0/src/gac/utils.py +0 -132
- {gac-2.0.0 → gac-2.2.0}/.gitignore +0 -0
- {gac-2.0.0 → gac-2.2.0}/LICENSE +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/__init__.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/config.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/config_cli.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/diff_cli.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/errors.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/git.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/preprocess.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/prompt.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/anthropic.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/cerebras.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/chutes.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/custom_anthropic.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/custom_openai.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/deepseek.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/fireworks.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/gemini.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/groq.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/lmstudio.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/minimax.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/ollama.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/openai.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/openrouter.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/streamlake.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/synthetic.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/together.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/providers/zai.py +0 -0
- {gac-2.0.0 → gac-2.2.0}/src/gac/security.py +0 -0
{gac-2.0.0 → gac-2.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
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
|
|
@@ -25,6 +25,7 @@ Requires-Dist: click>=8.3.0
|
|
|
25
25
|
Requires-Dist: halo
|
|
26
26
|
Requires-Dist: httpcore>=1.0.9
|
|
27
27
|
Requires-Dist: httpx>=0.28.0
|
|
28
|
+
Requires-Dist: prompt-toolkit>=3.0.36
|
|
28
29
|
Requires-Dist: pydantic>=2.12.0
|
|
29
30
|
Requires-Dist: python-dotenv>=1.1.1
|
|
30
31
|
Requires-Dist: questionary
|
|
@@ -52,9 +53,9 @@ Description-Content-Type: text/markdown
|
|
|
52
53
|
[](docs/CONTRIBUTING.md)
|
|
53
54
|
[](LICENSE)
|
|
54
55
|
|
|
55
|
-
**LLM-powered commit messages that understand your code
|
|
56
|
+
**LLM-powered commit messages that understand your code!**
|
|
56
57
|
|
|
57
|
-
**
|
|
58
|
+
**Automate your commits!** Replace `git commit -m "..."` with `gac` for contextual, well-formatted commit messages generated by large language models!
|
|
58
59
|
|
|
59
60
|
---
|
|
60
61
|
|
|
@@ -68,7 +69,7 @@ Intelligent, contextual messages that explain the **why** behind your changes:
|
|
|
68
69
|
|
|
69
70
|
## Quick Start
|
|
70
71
|
|
|
71
|
-
### Use without installing
|
|
72
|
+
### Use gac without installing
|
|
72
73
|
|
|
73
74
|
```bash
|
|
74
75
|
uvx gac init # Configure your LLM provider
|
|
@@ -77,7 +78,7 @@ uvx gac # Generate and commit with LLM
|
|
|
77
78
|
|
|
78
79
|
That's it! Review the generated message and confirm with `y`.
|
|
79
80
|
|
|
80
|
-
### Install gac
|
|
81
|
+
### Install and use gac
|
|
81
82
|
|
|
82
83
|
```bash
|
|
83
84
|
uv tool install gac
|
|
@@ -85,6 +86,12 @@ gac init
|
|
|
85
86
|
gac
|
|
86
87
|
```
|
|
87
88
|
|
|
89
|
+
### Upgrade installed gac
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv tool upgrade gac
|
|
93
|
+
```
|
|
94
|
+
|
|
88
95
|
---
|
|
89
96
|
|
|
90
97
|
## Key Features
|
|
@@ -92,7 +99,7 @@ gac
|
|
|
92
99
|
### 🌐 **Supported Providers**
|
|
93
100
|
|
|
94
101
|
- **Anthropic** • **Cerebras** • **Chutes.ai** • **DeepSeek** • **Fireworks**
|
|
95
|
-
- **Gemini** • **Groq** • **LM Studio** • **MiniMax** • **Ollama** • **OpenAI**
|
|
102
|
+
- **Gemini** • **Groq** • **LM Studio** • **MiniMax** • **Mistral** • **Ollama** • **OpenAI**
|
|
96
103
|
- **OpenRouter** • **Streamlake** • **Synthetic.new** • **Together AI**
|
|
97
104
|
- **Z.AI** • **Z.AI Coding** • **Custom Endpoints (Anthropic/OpenAI)**
|
|
98
105
|
|
|
@@ -117,7 +124,7 @@ gac
|
|
|
117
124
|
|
|
118
125
|
### 💻 **Developer Experience**
|
|
119
126
|
|
|
120
|
-
- **Interactive feedback**: Type `r` to reroll, or directly type your feedback like `make it shorter` or `focus on the bug fix`
|
|
127
|
+
- **Interactive feedback**: Type `r` to reroll, `e` to edit in-place with vi/emacs keybindings, or directly type your feedback like `make it shorter` or `focus on the bug fix`
|
|
121
128
|
- **One-command workflows**: Complete workflows with flags like `gac -ayp` (stage all, auto-confirm, push)
|
|
122
129
|
- **Git integration**: Respects pre-commit and lefthook hooks, running them before expensive LLM operations
|
|
123
130
|
|
|
@@ -140,7 +147,7 @@ git add .
|
|
|
140
147
|
# Generate and commit with LLM
|
|
141
148
|
gac
|
|
142
149
|
|
|
143
|
-
# Review → y (commit) | n (cancel) | r (reroll) | or type feedback
|
|
150
|
+
# Review → y (commit) | n (cancel) | r (reroll) | e (edit) | or type feedback
|
|
144
151
|
```
|
|
145
152
|
|
|
146
153
|
### Common Commands
|
|
@@ -177,13 +184,18 @@ gac --skip-secret-scan
|
|
|
177
184
|
|
|
178
185
|
### Interactive Feedback System
|
|
179
186
|
|
|
180
|
-
Not happy with the result? You have
|
|
187
|
+
Not happy with the result? You have several options:
|
|
181
188
|
|
|
182
189
|
```bash
|
|
183
190
|
# Simple reroll (no feedback)
|
|
184
191
|
r
|
|
185
192
|
|
|
186
|
-
#
|
|
193
|
+
# Edit in-place with rich terminal editing
|
|
194
|
+
e
|
|
195
|
+
# Uses prompt_toolkit for multi-line editing with vi/emacs keybindings
|
|
196
|
+
# Press Esc+Enter or Ctrl+S to submit, Ctrl+C to cancel
|
|
197
|
+
|
|
198
|
+
# Or just type your feedback directly!
|
|
187
199
|
make it shorter and focus on the performance improvement
|
|
188
200
|
use conventional commit format with scope
|
|
189
201
|
explain the security implications
|
|
@@ -191,6 +203,13 @@ explain the security implications
|
|
|
191
203
|
# Press Enter on empty input to see the prompt again
|
|
192
204
|
```
|
|
193
205
|
|
|
206
|
+
The edit feature (`e`) provides rich in-place terminal editing, allowing you to:
|
|
207
|
+
|
|
208
|
+
- **Edit naturally**: Multi-line editing with familiar vi/emacs key bindings
|
|
209
|
+
- **Make quick fixes**: Correct typos, adjust wording, or refine formatting
|
|
210
|
+
- **Add details**: Include information the LLM might have missed
|
|
211
|
+
- **Restructure**: Reorganize bullet points or change the message structure
|
|
212
|
+
|
|
194
213
|
---
|
|
195
214
|
|
|
196
215
|
## Configuration
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
[](docs/CONTRIBUTING.md)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
|
|
14
|
-
**LLM-powered commit messages that understand your code
|
|
14
|
+
**LLM-powered commit messages that understand your code!**
|
|
15
15
|
|
|
16
|
-
**
|
|
16
|
+
**Automate your commits!** Replace `git commit -m "..."` with `gac` for contextual, well-formatted commit messages generated by large language models!
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -27,7 +27,7 @@ Intelligent, contextual messages that explain the **why** behind your changes:
|
|
|
27
27
|
|
|
28
28
|
## Quick Start
|
|
29
29
|
|
|
30
|
-
### Use without installing
|
|
30
|
+
### Use gac without installing
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
uvx gac init # Configure your LLM provider
|
|
@@ -36,7 +36,7 @@ uvx gac # Generate and commit with LLM
|
|
|
36
36
|
|
|
37
37
|
That's it! Review the generated message and confirm with `y`.
|
|
38
38
|
|
|
39
|
-
### Install gac
|
|
39
|
+
### Install and use gac
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
uv tool install gac
|
|
@@ -44,6 +44,12 @@ gac init
|
|
|
44
44
|
gac
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### Upgrade installed gac
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv tool upgrade gac
|
|
51
|
+
```
|
|
52
|
+
|
|
47
53
|
---
|
|
48
54
|
|
|
49
55
|
## Key Features
|
|
@@ -51,7 +57,7 @@ gac
|
|
|
51
57
|
### 🌐 **Supported Providers**
|
|
52
58
|
|
|
53
59
|
- **Anthropic** • **Cerebras** • **Chutes.ai** • **DeepSeek** • **Fireworks**
|
|
54
|
-
- **Gemini** • **Groq** • **LM Studio** • **MiniMax** • **Ollama** • **OpenAI**
|
|
60
|
+
- **Gemini** • **Groq** • **LM Studio** • **MiniMax** • **Mistral** • **Ollama** • **OpenAI**
|
|
55
61
|
- **OpenRouter** • **Streamlake** • **Synthetic.new** • **Together AI**
|
|
56
62
|
- **Z.AI** • **Z.AI Coding** • **Custom Endpoints (Anthropic/OpenAI)**
|
|
57
63
|
|
|
@@ -76,7 +82,7 @@ gac
|
|
|
76
82
|
|
|
77
83
|
### 💻 **Developer Experience**
|
|
78
84
|
|
|
79
|
-
- **Interactive feedback**: Type `r` to reroll, or directly type your feedback like `make it shorter` or `focus on the bug fix`
|
|
85
|
+
- **Interactive feedback**: Type `r` to reroll, `e` to edit in-place with vi/emacs keybindings, or directly type your feedback like `make it shorter` or `focus on the bug fix`
|
|
80
86
|
- **One-command workflows**: Complete workflows with flags like `gac -ayp` (stage all, auto-confirm, push)
|
|
81
87
|
- **Git integration**: Respects pre-commit and lefthook hooks, running them before expensive LLM operations
|
|
82
88
|
|
|
@@ -99,7 +105,7 @@ git add .
|
|
|
99
105
|
# Generate and commit with LLM
|
|
100
106
|
gac
|
|
101
107
|
|
|
102
|
-
# Review → y (commit) | n (cancel) | r (reroll) | or type feedback
|
|
108
|
+
# Review → y (commit) | n (cancel) | r (reroll) | e (edit) | or type feedback
|
|
103
109
|
```
|
|
104
110
|
|
|
105
111
|
### Common Commands
|
|
@@ -136,13 +142,18 @@ gac --skip-secret-scan
|
|
|
136
142
|
|
|
137
143
|
### Interactive Feedback System
|
|
138
144
|
|
|
139
|
-
Not happy with the result? You have
|
|
145
|
+
Not happy with the result? You have several options:
|
|
140
146
|
|
|
141
147
|
```bash
|
|
142
148
|
# Simple reroll (no feedback)
|
|
143
149
|
r
|
|
144
150
|
|
|
145
|
-
#
|
|
151
|
+
# Edit in-place with rich terminal editing
|
|
152
|
+
e
|
|
153
|
+
# Uses prompt_toolkit for multi-line editing with vi/emacs keybindings
|
|
154
|
+
# Press Esc+Enter or Ctrl+S to submit, Ctrl+C to cancel
|
|
155
|
+
|
|
156
|
+
# Or just type your feedback directly!
|
|
146
157
|
make it shorter and focus on the performance improvement
|
|
147
158
|
use conventional commit format with scope
|
|
148
159
|
explain the security implications
|
|
@@ -150,6 +161,13 @@ explain the security implications
|
|
|
150
161
|
# Press Enter on empty input to see the prompt again
|
|
151
162
|
```
|
|
152
163
|
|
|
164
|
+
The edit feature (`e`) provides rich in-place terminal editing, allowing you to:
|
|
165
|
+
|
|
166
|
+
- **Edit naturally**: Multi-line editing with familiar vi/emacs key bindings
|
|
167
|
+
- **Make quick fixes**: Correct typos, adjust wording, or refine formatting
|
|
168
|
+
- **Add details**: Include information the LLM might have missed
|
|
169
|
+
- **Restructure**: Reorganize bullet points or change the message structure
|
|
170
|
+
|
|
153
171
|
---
|
|
154
172
|
|
|
155
173
|
## Configuration
|
|
@@ -21,6 +21,7 @@ from gac.providers import (
|
|
|
21
21
|
call_groq_api,
|
|
22
22
|
call_lmstudio_api,
|
|
23
23
|
call_minimax_api,
|
|
24
|
+
call_mistral_api,
|
|
24
25
|
call_ollama_api,
|
|
25
26
|
call_openai_api,
|
|
26
27
|
call_openrouter_api,
|
|
@@ -94,6 +95,7 @@ def generate_commit_message(
|
|
|
94
95
|
"groq": call_groq_api,
|
|
95
96
|
"lm-studio": call_lmstudio_api,
|
|
96
97
|
"minimax": call_minimax_api,
|
|
98
|
+
"mistral": call_mistral_api,
|
|
97
99
|
"ollama": call_ollama_api,
|
|
98
100
|
"openai": call_openai_api,
|
|
99
101
|
"openrouter": call_openrouter_api,
|
|
@@ -154,5 +154,15 @@ cli.add_command(init_cli)
|
|
|
154
154
|
cli.add_command(language_cli)
|
|
155
155
|
cli.add_command(diff_cli)
|
|
156
156
|
|
|
157
|
+
|
|
158
|
+
@click.command(context_settings=language_cli.context_settings)
|
|
159
|
+
@click.pass_context
|
|
160
|
+
def lang(ctx):
|
|
161
|
+
"""Set the language for commit messages interactively. (Alias for 'language')"""
|
|
162
|
+
ctx.forward(language_cli)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
cli.add_command(lang) # Add the lang alias
|
|
166
|
+
|
|
157
167
|
if __name__ == "__main__":
|
|
158
168
|
cli()
|
|
@@ -21,7 +21,7 @@ class EnvDefaults:
|
|
|
21
21
|
MAX_RETRIES: int = 3
|
|
22
22
|
TEMPERATURE: float = 1
|
|
23
23
|
MAX_OUTPUT_TOKENS: int = 1024 # includes reasoning tokens
|
|
24
|
-
WARNING_LIMIT_TOKENS: int =
|
|
24
|
+
WARNING_LIMIT_TOKENS: int = 32768
|
|
25
25
|
ALWAYS_INCLUDE_SCOPE: bool = False
|
|
26
26
|
SKIP_SECRET_SCAN: bool = False
|
|
27
27
|
VERBOSE: bool = False
|
|
@@ -214,6 +214,37 @@ class Languages:
|
|
|
214
214
|
"fi": "Finnish",
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
# List of languages with display names and English names for CLI selection
|
|
218
|
+
# Format: (display_name, english_name)
|
|
219
|
+
LANGUAGES: list[tuple[str, str]] = [
|
|
220
|
+
("English", "English"),
|
|
221
|
+
("简体中文", "Simplified Chinese"),
|
|
222
|
+
("繁體中文", "Traditional Chinese"),
|
|
223
|
+
("日本語", "Japanese"),
|
|
224
|
+
("한국어", "Korean"),
|
|
225
|
+
("Español", "Spanish"),
|
|
226
|
+
("Português", "Portuguese"),
|
|
227
|
+
("Français", "French"),
|
|
228
|
+
("Deutsch", "German"),
|
|
229
|
+
("Русский", "Russian"),
|
|
230
|
+
("हिन्दी", "Hindi"),
|
|
231
|
+
("Italiano", "Italian"),
|
|
232
|
+
("Polski", "Polish"),
|
|
233
|
+
("Türkçe", "Turkish"),
|
|
234
|
+
("Nederlands", "Dutch"),
|
|
235
|
+
("Tiếng Việt", "Vietnamese"),
|
|
236
|
+
("ไทย", "Thai"),
|
|
237
|
+
("Bahasa Indonesia", "Indonesian"),
|
|
238
|
+
("Svenska", "Swedish"),
|
|
239
|
+
("العربية", "Arabic"),
|
|
240
|
+
("עברית", "Hebrew"),
|
|
241
|
+
("Ελληνικά", "Greek"),
|
|
242
|
+
("Dansk", "Danish"),
|
|
243
|
+
("Norsk", "Norwegian"),
|
|
244
|
+
("Suomi", "Finnish"),
|
|
245
|
+
("Custom", "Custom"),
|
|
246
|
+
]
|
|
247
|
+
|
|
217
248
|
@staticmethod
|
|
218
249
|
def resolve_code(language: str) -> str:
|
|
219
250
|
"""Resolve a language code to its full name.
|
|
@@ -6,6 +6,8 @@ import click
|
|
|
6
6
|
import questionary
|
|
7
7
|
from dotenv import set_key
|
|
8
8
|
|
|
9
|
+
from gac.constants import Languages
|
|
10
|
+
|
|
9
11
|
GAC_ENV_PATH = Path.home() / ".gac.env"
|
|
10
12
|
|
|
11
13
|
|
|
@@ -33,7 +35,7 @@ def init() -> None:
|
|
|
33
35
|
|
|
34
36
|
providers = [
|
|
35
37
|
("Anthropic", "claude-haiku-4-5"),
|
|
36
|
-
("Cerebras", "
|
|
38
|
+
("Cerebras", "zai-glm-4.6"),
|
|
37
39
|
("Chutes", "zai-org/GLM-4.6-FP8"),
|
|
38
40
|
("Custom (Anthropic)", ""),
|
|
39
41
|
("Custom (OpenAI)", ""),
|
|
@@ -43,6 +45,7 @@ def init() -> None:
|
|
|
43
45
|
("Groq", "meta-llama/llama-4-maverick-17b-128e-instruct"),
|
|
44
46
|
("LM Studio", "gemma3"),
|
|
45
47
|
("MiniMax", "MiniMax-M2"),
|
|
48
|
+
("Mistral", "mistral-small-latest"),
|
|
46
49
|
("Ollama", "gemma3"),
|
|
47
50
|
("OpenAI", "gpt-4.1-mini"),
|
|
48
51
|
("OpenRouter", "openrouter/auto"),
|
|
@@ -153,36 +156,7 @@ def init() -> None:
|
|
|
153
156
|
|
|
154
157
|
# Language selection
|
|
155
158
|
click.echo("\n")
|
|
156
|
-
|
|
157
|
-
("English", "English"),
|
|
158
|
-
("简体中文", "Simplified Chinese"),
|
|
159
|
-
("繁體中文", "Traditional Chinese"),
|
|
160
|
-
("日本語", "Japanese"),
|
|
161
|
-
("한국어", "Korean"),
|
|
162
|
-
("Español", "Spanish"),
|
|
163
|
-
("Português", "Portuguese"),
|
|
164
|
-
("Français", "French"),
|
|
165
|
-
("Deutsch", "German"),
|
|
166
|
-
("Русский", "Russian"),
|
|
167
|
-
("हिन्दी", "Hindi"),
|
|
168
|
-
("Italiano", "Italian"),
|
|
169
|
-
("Polski", "Polish"),
|
|
170
|
-
("Türkçe", "Turkish"),
|
|
171
|
-
("Nederlands", "Dutch"),
|
|
172
|
-
("Tiếng Việt", "Vietnamese"),
|
|
173
|
-
("ไทย", "Thai"),
|
|
174
|
-
("Bahasa Indonesia", "Indonesian"),
|
|
175
|
-
("Svenska", "Swedish"),
|
|
176
|
-
("العربية", "Arabic"),
|
|
177
|
-
("עברית", "Hebrew"),
|
|
178
|
-
("Ελληνικά", "Greek"),
|
|
179
|
-
("Dansk", "Danish"),
|
|
180
|
-
("Norsk", "Norwegian"),
|
|
181
|
-
("Suomi", "Finnish"),
|
|
182
|
-
("Custom", "Custom"),
|
|
183
|
-
]
|
|
184
|
-
|
|
185
|
-
display_names = [lang[0] for lang in languages]
|
|
159
|
+
display_names = [lang[0] for lang in Languages.LANGUAGES]
|
|
186
160
|
language_selection = questionary.select(
|
|
187
161
|
"Select a language for commit messages:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
|
|
188
162
|
).ask()
|
|
@@ -202,7 +176,7 @@ def init() -> None:
|
|
|
202
176
|
language_value = custom_language.strip()
|
|
203
177
|
else:
|
|
204
178
|
# Find the English name for the selected language
|
|
205
|
-
language_value = next(lang[1] for lang in
|
|
179
|
+
language_value = next(lang[1] for lang in Languages.LANGUAGES if lang[0] == language_selection)
|
|
206
180
|
|
|
207
181
|
if language_value:
|
|
208
182
|
# Ask about prefix translation
|
|
@@ -6,6 +6,8 @@ import click
|
|
|
6
6
|
import questionary
|
|
7
7
|
from dotenv import set_key, unset_key
|
|
8
8
|
|
|
9
|
+
from gac.constants import Languages
|
|
10
|
+
|
|
9
11
|
GAC_ENV_PATH = Path.home() / ".gac.env"
|
|
10
12
|
|
|
11
13
|
|
|
@@ -14,38 +16,7 @@ def language() -> None:
|
|
|
14
16
|
"""Set the language for commit messages interactively."""
|
|
15
17
|
click.echo("Select a language for your commit messages:\n")
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
# Based on GitHub statistics and global developer demographics
|
|
19
|
-
languages = [
|
|
20
|
-
("English", "English"),
|
|
21
|
-
("简体中文", "Simplified Chinese"),
|
|
22
|
-
("繁體中文", "Traditional Chinese"),
|
|
23
|
-
("日本語", "Japanese"),
|
|
24
|
-
("한국어", "Korean"),
|
|
25
|
-
("Español", "Spanish"),
|
|
26
|
-
("Português", "Portuguese"),
|
|
27
|
-
("Français", "French"),
|
|
28
|
-
("Deutsch", "German"),
|
|
29
|
-
("Русский", "Russian"),
|
|
30
|
-
("हिन्दी", "Hindi"),
|
|
31
|
-
("Italiano", "Italian"),
|
|
32
|
-
("Polski", "Polish"),
|
|
33
|
-
("Türkçe", "Turkish"),
|
|
34
|
-
("Nederlands", "Dutch"),
|
|
35
|
-
("Tiếng Việt", "Vietnamese"),
|
|
36
|
-
("ไทย", "Thai"),
|
|
37
|
-
("Bahasa Indonesia", "Indonesian"),
|
|
38
|
-
("Svenska", "Swedish"),
|
|
39
|
-
("العربية", "Arabic"),
|
|
40
|
-
("עברית", "Hebrew"),
|
|
41
|
-
("Ελληνικά", "Greek"),
|
|
42
|
-
("Dansk", "Danish"),
|
|
43
|
-
("Norsk", "Norwegian"),
|
|
44
|
-
("Suomi", "Finnish"),
|
|
45
|
-
("Custom", "Custom"),
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
display_names = [lang[0] for lang in languages]
|
|
19
|
+
display_names = [lang[0] for lang in Languages.LANGUAGES]
|
|
49
20
|
selection = questionary.select(
|
|
50
21
|
"Choose your language:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
|
|
51
22
|
).ask()
|
|
@@ -78,7 +49,7 @@ def language() -> None:
|
|
|
78
49
|
language_value = custom_language.strip()
|
|
79
50
|
else:
|
|
80
51
|
# Find the English name for the selected language
|
|
81
|
-
language_value = next(lang[1] for lang in
|
|
52
|
+
language_value = next(lang[1] for lang in Languages.LANGUAGES if lang[0] == selection)
|
|
82
53
|
|
|
83
54
|
# Ask about prefix translation
|
|
84
55
|
click.echo() # Blank line for spacing
|
|
@@ -25,6 +25,7 @@ from gac.git import (
|
|
|
25
25
|
from gac.preprocess import preprocess_diff
|
|
26
26
|
from gac.prompt import build_prompt, clean_commit_message
|
|
27
27
|
from gac.security import get_affected_files, scan_staged_diff
|
|
28
|
+
from gac.utils import edit_commit_message_inplace
|
|
28
29
|
|
|
29
30
|
logger = logging.getLogger(__name__)
|
|
30
31
|
|
|
@@ -277,7 +278,7 @@ def main(
|
|
|
277
278
|
if require_confirmation:
|
|
278
279
|
while True:
|
|
279
280
|
response = click.prompt(
|
|
280
|
-
"Proceed with commit above? [y/n/r/<feedback>]",
|
|
281
|
+
"Proceed with commit above? [y/n/r/e/<feedback>]",
|
|
281
282
|
type=str,
|
|
282
283
|
show_default=False,
|
|
283
284
|
).strip()
|
|
@@ -290,6 +291,18 @@ def main(
|
|
|
290
291
|
sys.exit(0)
|
|
291
292
|
if response == "":
|
|
292
293
|
continue
|
|
294
|
+
if response_lower in ["e", "edit"]:
|
|
295
|
+
edited_message = edit_commit_message_inplace(commit_message)
|
|
296
|
+
if edited_message:
|
|
297
|
+
commit_message = edited_message
|
|
298
|
+
conversation_messages[-1] = {"role": "assistant", "content": commit_message}
|
|
299
|
+
logger.info("Commit message edited by user")
|
|
300
|
+
console.print("\n[bold green]Edited commit message:[/bold green]")
|
|
301
|
+
console.print(Panel(commit_message, title="Commit Message", border_style="cyan"))
|
|
302
|
+
else:
|
|
303
|
+
console.print("[yellow]Using previous message.[/yellow]")
|
|
304
|
+
console.print(Panel(commit_message, title="Commit Message", border_style="cyan"))
|
|
305
|
+
continue
|
|
293
306
|
if response_lower in ["r", "reroll"]:
|
|
294
307
|
feedback_message = (
|
|
295
308
|
"Please provide an alternative commit message using the same repository context."
|
|
@@ -11,6 +11,7 @@ from .gemini import call_gemini_api
|
|
|
11
11
|
from .groq import call_groq_api
|
|
12
12
|
from .lmstudio import call_lmstudio_api
|
|
13
13
|
from .minimax import call_minimax_api
|
|
14
|
+
from .mistral import call_mistral_api
|
|
14
15
|
from .ollama import call_ollama_api
|
|
15
16
|
from .openai import call_openai_api
|
|
16
17
|
from .openrouter import call_openrouter_api
|
|
@@ -31,6 +32,7 @@ __all__ = [
|
|
|
31
32
|
"call_groq_api",
|
|
32
33
|
"call_lmstudio_api",
|
|
33
34
|
"call_minimax_api",
|
|
35
|
+
"call_mistral_api",
|
|
34
36
|
"call_ollama_api",
|
|
35
37
|
"call_openai_api",
|
|
36
38
|
"call_openrouter_api",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Mistral API provider for gac."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from gac.errors import AIError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def call_mistral_api(model: str, messages: list[dict], temperature: float, max_tokens: int) -> str:
|
|
11
|
+
"""Call Mistral API directly."""
|
|
12
|
+
api_key = os.getenv("MISTRAL_API_KEY")
|
|
13
|
+
if not api_key:
|
|
14
|
+
raise AIError.authentication_error("MISTRAL_API_KEY not found in environment variables")
|
|
15
|
+
|
|
16
|
+
url = "https://api.mistral.ai/v1/chat/completions"
|
|
17
|
+
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
18
|
+
|
|
19
|
+
data = {"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens}
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
response = httpx.post(url, headers=headers, json=data, timeout=120)
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
response_data = response.json()
|
|
25
|
+
content = response_data["choices"][0]["message"]["content"]
|
|
26
|
+
if content is None:
|
|
27
|
+
raise AIError.model_error("Mistral API returned null content")
|
|
28
|
+
if content == "":
|
|
29
|
+
raise AIError.model_error("Mistral API returned empty content")
|
|
30
|
+
return content
|
|
31
|
+
except httpx.HTTPStatusError as e:
|
|
32
|
+
if e.response.status_code == 429:
|
|
33
|
+
raise AIError.rate_limit_error(f"Mistral API rate limit exceeded: {e.response.text}") from e
|
|
34
|
+
raise AIError.model_error(f"Mistral API error: {e.response.status_code} - {e.response.text}") from e
|
|
35
|
+
except httpx.TimeoutException as e:
|
|
36
|
+
raise AIError.timeout_error(f"Mistral API request timed out: {str(e)}") from e
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise AIError.model_error(f"Error calling Mistral API: {str(e)}") from e
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Utility functions for gac."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.theme import Theme
|
|
8
|
+
|
|
9
|
+
from gac.constants import Logging
|
|
10
|
+
from gac.errors import GacError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def setup_logging(
|
|
14
|
+
log_level: int | str = Logging.DEFAULT_LEVEL,
|
|
15
|
+
quiet: bool = False,
|
|
16
|
+
force: bool = False,
|
|
17
|
+
suppress_noisy: bool = False,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Configure logging for the application.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
log_level: Log level to use (DEBUG, INFO, WARNING, ERROR)
|
|
23
|
+
quiet: If True, suppress all output except errors
|
|
24
|
+
force: If True, force reconfiguration of logging
|
|
25
|
+
suppress_noisy: If True, suppress noisy third-party loggers
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(log_level, str):
|
|
28
|
+
log_level = getattr(logging, log_level.upper(), logging.WARNING)
|
|
29
|
+
|
|
30
|
+
if quiet:
|
|
31
|
+
log_level = logging.ERROR
|
|
32
|
+
|
|
33
|
+
kwargs = {"force": force} if force else {}
|
|
34
|
+
|
|
35
|
+
logging.basicConfig(
|
|
36
|
+
level=log_level,
|
|
37
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
38
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
39
|
+
**kwargs, # type: ignore[arg-type]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if suppress_noisy:
|
|
43
|
+
for noisy_logger in ["requests", "urllib3"]:
|
|
44
|
+
logging.getLogger(noisy_logger).setLevel(logging.WARNING)
|
|
45
|
+
|
|
46
|
+
logger.info(f"Logging initialized with level: {logging.getLevelName(log_level)}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
theme = Theme(
|
|
50
|
+
{
|
|
51
|
+
"success": "green bold",
|
|
52
|
+
"info": "blue",
|
|
53
|
+
"warning": "yellow",
|
|
54
|
+
"error": "red bold",
|
|
55
|
+
"header": "magenta",
|
|
56
|
+
"notification": "bright_cyan bold",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
console = Console(theme=theme)
|
|
60
|
+
logger = logging.getLogger(__name__)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def print_message(message: str, level: str = "info") -> None:
|
|
64
|
+
"""Print a styled message with the specified level."""
|
|
65
|
+
console.print(message, style=level)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run_subprocess(
|
|
69
|
+
command: list[str],
|
|
70
|
+
silent: bool = False,
|
|
71
|
+
timeout: int = 60,
|
|
72
|
+
check: bool = True,
|
|
73
|
+
strip_output: bool = True,
|
|
74
|
+
raise_on_error: bool = True,
|
|
75
|
+
) -> str:
|
|
76
|
+
"""Run a subprocess command safely and return the output.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
command: List of command arguments
|
|
80
|
+
silent: If True, suppress debug logging
|
|
81
|
+
timeout: Command timeout in seconds
|
|
82
|
+
check: Whether to check return code (for compatibility)
|
|
83
|
+
strip_output: Whether to strip whitespace from output
|
|
84
|
+
raise_on_error: Whether to raise an exception on error
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Command output as string
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
GacError: If the command times out
|
|
91
|
+
subprocess.CalledProcessError: If the command fails and raise_on_error is True
|
|
92
|
+
"""
|
|
93
|
+
if not silent:
|
|
94
|
+
logger.debug(f"Running command: {' '.join(command)}")
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
result = subprocess.run(
|
|
98
|
+
command,
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
check=False,
|
|
102
|
+
timeout=timeout,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
should_raise = result.returncode != 0 and (check or raise_on_error)
|
|
106
|
+
|
|
107
|
+
if should_raise:
|
|
108
|
+
if not silent:
|
|
109
|
+
logger.debug(f"Command stderr: {result.stderr}")
|
|
110
|
+
raise subprocess.CalledProcessError(result.returncode, command, result.stdout, result.stderr)
|
|
111
|
+
|
|
112
|
+
output = result.stdout
|
|
113
|
+
if strip_output:
|
|
114
|
+
output = output.strip()
|
|
115
|
+
|
|
116
|
+
return output
|
|
117
|
+
except subprocess.TimeoutExpired as e:
|
|
118
|
+
logger.error(f"Command timed out after {timeout} seconds: {' '.join(command)}")
|
|
119
|
+
raise GacError(f"Command timed out: {' '.join(command)}") from e
|
|
120
|
+
except subprocess.CalledProcessError as e:
|
|
121
|
+
if not silent:
|
|
122
|
+
logger.error(f"Command failed: {e.stderr.strip() if e.stderr else str(e)}")
|
|
123
|
+
if raise_on_error:
|
|
124
|
+
raise
|
|
125
|
+
return ""
|
|
126
|
+
except Exception as e:
|
|
127
|
+
if not silent:
|
|
128
|
+
logger.debug(f"Command error: {e}")
|
|
129
|
+
if raise_on_error:
|
|
130
|
+
# Convert generic exceptions to CalledProcessError for consistency
|
|
131
|
+
raise subprocess.CalledProcessError(1, command, "", str(e)) from e
|
|
132
|
+
return ""
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def edit_commit_message_inplace(message: str) -> str | None:
|
|
136
|
+
"""Edit commit message in-place using rich terminal editing.
|
|
137
|
+
|
|
138
|
+
Uses prompt_toolkit to provide a rich editing experience with:
|
|
139
|
+
- Multi-line editing
|
|
140
|
+
- Vi/Emacs key bindings
|
|
141
|
+
- Line editing capabilities
|
|
142
|
+
- Esc+Enter or Ctrl+S to submit
|
|
143
|
+
- Ctrl+C to cancel
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
message: The initial commit message
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The edited commit message, or None if editing was cancelled
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> edited = edit_commit_message_inplace("feat: add feature")
|
|
153
|
+
>>> # User can edit the message using vi/emacs key bindings
|
|
154
|
+
>>> # Press Esc+Enter or Ctrl+S to submit
|
|
155
|
+
"""
|
|
156
|
+
from prompt_toolkit import Application
|
|
157
|
+
from prompt_toolkit.buffer import Buffer
|
|
158
|
+
from prompt_toolkit.document import Document
|
|
159
|
+
from prompt_toolkit.enums import EditingMode
|
|
160
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
161
|
+
from prompt_toolkit.layout import HSplit, Layout, Window
|
|
162
|
+
from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
|
|
163
|
+
from prompt_toolkit.layout.margins import ScrollbarMargin
|
|
164
|
+
from prompt_toolkit.styles import Style
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
console.print("\n[info]Edit commit message:[/info]")
|
|
168
|
+
console.print()
|
|
169
|
+
|
|
170
|
+
# Create buffer for text editing
|
|
171
|
+
text_buffer = Buffer(
|
|
172
|
+
document=Document(text=message, cursor_position=0),
|
|
173
|
+
multiline=True,
|
|
174
|
+
enable_history_search=False,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Track submission state
|
|
178
|
+
cancelled = {"value": False}
|
|
179
|
+
submitted = {"value": False}
|
|
180
|
+
|
|
181
|
+
# Create text editor window
|
|
182
|
+
text_window = Window(
|
|
183
|
+
content=BufferControl(
|
|
184
|
+
buffer=text_buffer,
|
|
185
|
+
focus_on_click=True,
|
|
186
|
+
),
|
|
187
|
+
height=lambda: max(5, message.count("\n") + 3),
|
|
188
|
+
wrap_lines=True,
|
|
189
|
+
right_margins=[ScrollbarMargin()],
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Create hint window
|
|
193
|
+
hint_window = Window(
|
|
194
|
+
content=FormattedTextControl(
|
|
195
|
+
text=[("class:hint", " Esc+Enter or Ctrl+S to submit | Ctrl+C to cancel ")],
|
|
196
|
+
),
|
|
197
|
+
height=1,
|
|
198
|
+
dont_extend_height=True,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Create layout
|
|
202
|
+
root_container = HSplit(
|
|
203
|
+
[
|
|
204
|
+
text_window,
|
|
205
|
+
hint_window,
|
|
206
|
+
]
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
layout = Layout(root_container, focused_element=text_window)
|
|
210
|
+
|
|
211
|
+
# Create key bindings
|
|
212
|
+
kb = KeyBindings()
|
|
213
|
+
|
|
214
|
+
@kb.add("c-s")
|
|
215
|
+
def _(event):
|
|
216
|
+
"""Submit with Ctrl+S."""
|
|
217
|
+
submitted["value"] = True
|
|
218
|
+
event.app.exit()
|
|
219
|
+
|
|
220
|
+
@kb.add("c-c")
|
|
221
|
+
def _(event):
|
|
222
|
+
"""Cancel editing."""
|
|
223
|
+
cancelled["value"] = True
|
|
224
|
+
event.app.exit()
|
|
225
|
+
|
|
226
|
+
@kb.add("escape", "enter")
|
|
227
|
+
def _(event):
|
|
228
|
+
"""Submit with Esc+Enter."""
|
|
229
|
+
submitted["value"] = True
|
|
230
|
+
event.app.exit()
|
|
231
|
+
|
|
232
|
+
# Create and run application
|
|
233
|
+
custom_style = Style.from_dict(
|
|
234
|
+
{
|
|
235
|
+
"hint": "#888888",
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
app: Application[None] = Application(
|
|
240
|
+
layout=layout,
|
|
241
|
+
key_bindings=kb,
|
|
242
|
+
full_screen=False,
|
|
243
|
+
mouse_support=False,
|
|
244
|
+
editing_mode=EditingMode.VI, # Enable vi key bindings
|
|
245
|
+
style=custom_style,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
app.run()
|
|
249
|
+
|
|
250
|
+
# Handle result
|
|
251
|
+
if cancelled["value"]:
|
|
252
|
+
console.print("\n[yellow]Edit cancelled.[/yellow]")
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
if submitted["value"]:
|
|
256
|
+
edited_message = text_buffer.text.strip()
|
|
257
|
+
if not edited_message:
|
|
258
|
+
console.print("[yellow]Commit message cannot be empty. Edit cancelled.[/yellow]")
|
|
259
|
+
return None
|
|
260
|
+
return edited_message
|
|
261
|
+
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
except (EOFError, KeyboardInterrupt):
|
|
265
|
+
console.print("\n[yellow]Edit cancelled.[/yellow]")
|
|
266
|
+
return None
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.error(f"Error during in-place editing: {e}")
|
|
269
|
+
console.print(f"[error]Failed to edit commit message: {e}[/error]")
|
|
270
|
+
return None
|
gac-2.0.0/src/gac/utils.py
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
"""Utility functions for gac."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import subprocess
|
|
5
|
-
|
|
6
|
-
from rich.console import Console
|
|
7
|
-
from rich.theme import Theme
|
|
8
|
-
|
|
9
|
-
from gac.constants import Logging
|
|
10
|
-
from gac.errors import GacError
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def setup_logging(
|
|
14
|
-
log_level: int | str = Logging.DEFAULT_LEVEL,
|
|
15
|
-
quiet: bool = False,
|
|
16
|
-
force: bool = False,
|
|
17
|
-
suppress_noisy: bool = False,
|
|
18
|
-
) -> None:
|
|
19
|
-
"""Configure logging for the application.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
log_level: Log level to use (DEBUG, INFO, WARNING, ERROR)
|
|
23
|
-
quiet: If True, suppress all output except errors
|
|
24
|
-
force: If True, force reconfiguration of logging
|
|
25
|
-
suppress_noisy: If True, suppress noisy third-party loggers
|
|
26
|
-
"""
|
|
27
|
-
if isinstance(log_level, str):
|
|
28
|
-
log_level = getattr(logging, log_level.upper(), logging.WARNING)
|
|
29
|
-
|
|
30
|
-
if quiet:
|
|
31
|
-
log_level = logging.ERROR
|
|
32
|
-
|
|
33
|
-
kwargs = {"force": force} if force else {}
|
|
34
|
-
|
|
35
|
-
logging.basicConfig(
|
|
36
|
-
level=log_level,
|
|
37
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
38
|
-
datefmt="%Y-%m-%d %H:%M:%S",
|
|
39
|
-
**kwargs, # type: ignore[arg-type]
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
if suppress_noisy:
|
|
43
|
-
for noisy_logger in ["requests", "urllib3"]:
|
|
44
|
-
logging.getLogger(noisy_logger).setLevel(logging.WARNING)
|
|
45
|
-
|
|
46
|
-
logger.info(f"Logging initialized with level: {logging.getLevelName(log_level)}")
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
theme = Theme(
|
|
50
|
-
{
|
|
51
|
-
"success": "green bold",
|
|
52
|
-
"info": "blue",
|
|
53
|
-
"warning": "yellow",
|
|
54
|
-
"error": "red bold",
|
|
55
|
-
"header": "magenta",
|
|
56
|
-
"notification": "bright_cyan bold",
|
|
57
|
-
}
|
|
58
|
-
)
|
|
59
|
-
console = Console(theme=theme)
|
|
60
|
-
logger = logging.getLogger(__name__)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def print_message(message: str, level: str = "info") -> None:
|
|
64
|
-
"""Print a styled message with the specified level."""
|
|
65
|
-
console.print(message, style=level)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def run_subprocess(
|
|
69
|
-
command: list[str],
|
|
70
|
-
silent: bool = False,
|
|
71
|
-
timeout: int = 60,
|
|
72
|
-
check: bool = True,
|
|
73
|
-
strip_output: bool = True,
|
|
74
|
-
raise_on_error: bool = True,
|
|
75
|
-
) -> str:
|
|
76
|
-
"""Run a subprocess command safely and return the output.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
command: List of command arguments
|
|
80
|
-
silent: If True, suppress debug logging
|
|
81
|
-
timeout: Command timeout in seconds
|
|
82
|
-
check: Whether to check return code (for compatibility)
|
|
83
|
-
strip_output: Whether to strip whitespace from output
|
|
84
|
-
raise_on_error: Whether to raise an exception on error
|
|
85
|
-
|
|
86
|
-
Returns:
|
|
87
|
-
Command output as string
|
|
88
|
-
|
|
89
|
-
Raises:
|
|
90
|
-
GacError: If the command times out
|
|
91
|
-
subprocess.CalledProcessError: If the command fails and raise_on_error is True
|
|
92
|
-
"""
|
|
93
|
-
if not silent:
|
|
94
|
-
logger.debug(f"Running command: {' '.join(command)}")
|
|
95
|
-
|
|
96
|
-
try:
|
|
97
|
-
result = subprocess.run(
|
|
98
|
-
command,
|
|
99
|
-
capture_output=True,
|
|
100
|
-
text=True,
|
|
101
|
-
check=False,
|
|
102
|
-
timeout=timeout,
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
should_raise = result.returncode != 0 and (check or raise_on_error)
|
|
106
|
-
|
|
107
|
-
if should_raise:
|
|
108
|
-
if not silent:
|
|
109
|
-
logger.debug(f"Command stderr: {result.stderr}")
|
|
110
|
-
raise subprocess.CalledProcessError(result.returncode, command, result.stdout, result.stderr)
|
|
111
|
-
|
|
112
|
-
output = result.stdout
|
|
113
|
-
if strip_output:
|
|
114
|
-
output = output.strip()
|
|
115
|
-
|
|
116
|
-
return output
|
|
117
|
-
except subprocess.TimeoutExpired as e:
|
|
118
|
-
logger.error(f"Command timed out after {timeout} seconds: {' '.join(command)}")
|
|
119
|
-
raise GacError(f"Command timed out: {' '.join(command)}") from e
|
|
120
|
-
except subprocess.CalledProcessError as e:
|
|
121
|
-
if not silent:
|
|
122
|
-
logger.error(f"Command failed: {e.stderr.strip() if e.stderr else str(e)}")
|
|
123
|
-
if raise_on_error:
|
|
124
|
-
raise
|
|
125
|
-
return ""
|
|
126
|
-
except Exception as e:
|
|
127
|
-
if not silent:
|
|
128
|
-
logger.debug(f"Command error: {e}")
|
|
129
|
-
if raise_on_error:
|
|
130
|
-
# Convert generic exceptions to CalledProcessError for consistency
|
|
131
|
-
raise subprocess.CalledProcessError(1, command, "", str(e)) from e
|
|
132
|
-
return ""
|
|
File without changes
|
{gac-2.0.0 → gac-2.2.0}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|