gac 2.1.0__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of gac might be problematic. Click here for more details.
- gac/__version__.py +1 -1
- gac/ai.py +26 -0
- gac/ai_utils.py +28 -13
- gac/cli.py +4 -0
- gac/constants.py +2 -2
- gac/git.py +42 -0
- gac/init_cli.py +174 -61
- gac/language_cli.py +8 -8
- gac/main.py +505 -156
- gac/prompt.py +101 -15
- gac/security.py +1 -1
- gac/utils.py +138 -0
- gac/workflow_utils.py +131 -0
- {gac-2.1.0.dist-info → gac-2.3.0.dist-info}/METADATA +21 -4
- {gac-2.1.0.dist-info → gac-2.3.0.dist-info}/RECORD +18 -17
- {gac-2.1.0.dist-info → gac-2.3.0.dist-info}/WHEEL +0 -0
- {gac-2.1.0.dist-info → gac-2.3.0.dist-info}/entry_points.txt +0 -0
- {gac-2.1.0.dist-info → gac-2.3.0.dist-info}/licenses/LICENSE +0 -0
gac/__version__.py
CHANGED
gac/ai.py
CHANGED
|
@@ -42,6 +42,8 @@ def generate_commit_message(
|
|
|
42
42
|
max_tokens: int = EnvDefaults.MAX_OUTPUT_TOKENS,
|
|
43
43
|
max_retries: int = EnvDefaults.MAX_RETRIES,
|
|
44
44
|
quiet: bool = False,
|
|
45
|
+
is_group: bool = False,
|
|
46
|
+
skip_success_message: bool = False,
|
|
45
47
|
) -> str:
|
|
46
48
|
"""Generate a commit message using direct API calls to AI providers.
|
|
47
49
|
|
|
@@ -116,6 +118,8 @@ def generate_commit_message(
|
|
|
116
118
|
max_tokens=max_tokens,
|
|
117
119
|
max_retries=max_retries,
|
|
118
120
|
quiet=quiet,
|
|
121
|
+
is_group=is_group,
|
|
122
|
+
skip_success_message=skip_success_message,
|
|
119
123
|
)
|
|
120
124
|
except AIError:
|
|
121
125
|
# Re-raise AIError exceptions as-is to preserve error classification
|
|
@@ -123,3 +127,25 @@ def generate_commit_message(
|
|
|
123
127
|
except Exception as e:
|
|
124
128
|
logger.error(f"Failed to generate commit message: {e}")
|
|
125
129
|
raise AIError.model_error(f"Failed to generate commit message: {e}") from e
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def generate_grouped_commits(
|
|
133
|
+
model: str,
|
|
134
|
+
prompt: list[dict[str, str]],
|
|
135
|
+
temperature: float,
|
|
136
|
+
max_tokens: int,
|
|
137
|
+
max_retries: int,
|
|
138
|
+
quiet: bool = False,
|
|
139
|
+
skip_success_message: bool = False,
|
|
140
|
+
) -> str:
|
|
141
|
+
"""Generate grouped commits JSON response."""
|
|
142
|
+
return generate_commit_message(
|
|
143
|
+
model=model,
|
|
144
|
+
prompt=prompt,
|
|
145
|
+
temperature=temperature,
|
|
146
|
+
max_tokens=max_tokens,
|
|
147
|
+
max_retries=max_retries,
|
|
148
|
+
quiet=quiet,
|
|
149
|
+
is_group=True,
|
|
150
|
+
skip_success_message=skip_success_message,
|
|
151
|
+
)
|
gac/ai_utils.py
CHANGED
|
@@ -83,6 +83,8 @@ def generate_with_retries(
|
|
|
83
83
|
max_tokens: int,
|
|
84
84
|
max_retries: int,
|
|
85
85
|
quiet: bool = False,
|
|
86
|
+
is_group: bool = False,
|
|
87
|
+
skip_success_message: bool = False,
|
|
86
88
|
) -> str:
|
|
87
89
|
"""Generate content with retry logic using direct API calls."""
|
|
88
90
|
# Parse model string to determine provider and actual model
|
|
@@ -121,10 +123,11 @@ def generate_with_retries(
|
|
|
121
123
|
raise AIError.model_error("No messages provided for AI generation")
|
|
122
124
|
|
|
123
125
|
# Set up spinner
|
|
126
|
+
message_type = "commit messages" if is_group else "commit message"
|
|
124
127
|
if quiet:
|
|
125
128
|
spinner = None
|
|
126
129
|
else:
|
|
127
|
-
spinner = Halo(text=f"Generating
|
|
130
|
+
spinner = Halo(text=f"Generating {message_type} with {provider} {model_name}...", spinner="dots")
|
|
128
131
|
spinner.start()
|
|
129
132
|
|
|
130
133
|
last_exception = None
|
|
@@ -132,7 +135,7 @@ def generate_with_retries(
|
|
|
132
135
|
|
|
133
136
|
for attempt in range(max_retries):
|
|
134
137
|
try:
|
|
135
|
-
if not quiet and attempt > 0:
|
|
138
|
+
if not quiet and not skip_success_message and attempt > 0:
|
|
136
139
|
if spinner:
|
|
137
140
|
spinner.text = f"Retry {attempt + 1}/{max_retries} with {provider} {model_name}..."
|
|
138
141
|
logger.info(f"Retry attempt {attempt + 1}/{max_retries}")
|
|
@@ -145,7 +148,10 @@ def generate_with_retries(
|
|
|
145
148
|
content = provider_func(model=model_name, messages=messages, temperature=temperature, max_tokens=max_tokens)
|
|
146
149
|
|
|
147
150
|
if spinner:
|
|
148
|
-
|
|
151
|
+
if skip_success_message:
|
|
152
|
+
spinner.stop() # Stop spinner without showing success/failure
|
|
153
|
+
else:
|
|
154
|
+
spinner.succeed(f"Generated {message_type} with {provider} {model_name}")
|
|
149
155
|
|
|
150
156
|
if content is not None and content.strip():
|
|
151
157
|
return content.strip() # type: ignore[no-any-return]
|
|
@@ -160,8 +166,8 @@ def generate_with_retries(
|
|
|
160
166
|
|
|
161
167
|
# For authentication and model errors, don't retry
|
|
162
168
|
if error_type in ["authentication", "model"]:
|
|
163
|
-
if spinner:
|
|
164
|
-
spinner.fail(f"Failed to generate
|
|
169
|
+
if spinner and not skip_success_message:
|
|
170
|
+
spinner.fail(f"Failed to generate {message_type} with {provider} {model_name}")
|
|
165
171
|
|
|
166
172
|
# Create the appropriate error type based on classification
|
|
167
173
|
if error_type == "authentication":
|
|
@@ -172,23 +178,32 @@ def generate_with_retries(
|
|
|
172
178
|
if attempt < max_retries - 1:
|
|
173
179
|
# Exponential backoff
|
|
174
180
|
wait_time = 2**attempt
|
|
175
|
-
if not quiet:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
if not quiet and not skip_success_message:
|
|
182
|
+
if attempt == 0:
|
|
183
|
+
logger.warning(f"AI generation failed, retrying in {wait_time}s: {str(e)}")
|
|
184
|
+
else:
|
|
185
|
+
logger.warning(
|
|
186
|
+
f"AI generation failed (attempt {attempt + 1}), retrying in {wait_time}s: {str(e)}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if spinner and not skip_success_message:
|
|
179
190
|
for i in range(wait_time, 0, -1):
|
|
180
191
|
spinner.text = f"Retry {attempt + 1}/{max_retries} in {i}s..."
|
|
181
192
|
time.sleep(1)
|
|
182
193
|
else:
|
|
183
194
|
time.sleep(wait_time)
|
|
184
195
|
else:
|
|
185
|
-
|
|
196
|
+
num_retries = max_retries
|
|
197
|
+
retry_word = "retry" if num_retries == 1 else "retries"
|
|
198
|
+
logger.error(f"AI generation failed after {num_retries} {retry_word}: {str(e)}")
|
|
186
199
|
|
|
187
|
-
if spinner:
|
|
188
|
-
spinner.fail(f"Failed to generate
|
|
200
|
+
if spinner and not skip_success_message:
|
|
201
|
+
spinner.fail(f"Failed to generate {message_type} with {provider} {model_name}")
|
|
189
202
|
|
|
190
203
|
# If we get here, all retries failed - use the last classified error type
|
|
191
|
-
|
|
204
|
+
num_retries = max_retries
|
|
205
|
+
retry_word = "retry" if num_retries == 1 else "retries"
|
|
206
|
+
error_message = f"Failed to generate {message_type} after {num_retries} {retry_word}"
|
|
192
207
|
if last_error_type == "authentication":
|
|
193
208
|
raise AIError.authentication_error(error_message) from last_exception
|
|
194
209
|
elif last_error_type == "rate_limit":
|
gac/cli.py
CHANGED
|
@@ -28,6 +28,7 @@ logger = logging.getLogger(__name__)
|
|
|
28
28
|
@click.group(invoke_without_command=True, context_settings={"ignore_unknown_options": True})
|
|
29
29
|
# Git workflow options
|
|
30
30
|
@click.option("--add-all", "-a", is_flag=True, help="Stage all changes before committing")
|
|
31
|
+
@click.option("--group", "-g", is_flag=True, help="Group changes into multiple logical commits")
|
|
31
32
|
@click.option("--push", "-p", is_flag=True, help="Push changes to remote after committing")
|
|
32
33
|
@click.option("--dry-run", is_flag=True, help="Dry run the commit workflow")
|
|
33
34
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
@@ -70,6 +71,7 @@ logger = logging.getLogger(__name__)
|
|
|
70
71
|
def cli(
|
|
71
72
|
ctx: click.Context,
|
|
72
73
|
add_all: bool = False,
|
|
74
|
+
group: bool = False,
|
|
73
75
|
log_level: str = str(config["log_level"]),
|
|
74
76
|
one_liner: bool = False,
|
|
75
77
|
push: bool = False,
|
|
@@ -109,6 +111,7 @@ def cli(
|
|
|
109
111
|
try:
|
|
110
112
|
main(
|
|
111
113
|
stage_all=add_all,
|
|
114
|
+
group=group,
|
|
112
115
|
model=model,
|
|
113
116
|
hint=hint,
|
|
114
117
|
one_liner=one_liner,
|
|
@@ -131,6 +134,7 @@ def cli(
|
|
|
131
134
|
|
|
132
135
|
ctx.obj = {
|
|
133
136
|
"add_all": add_all,
|
|
137
|
+
"group": group,
|
|
134
138
|
"log_level": log_level,
|
|
135
139
|
"one_liner": one_liner,
|
|
136
140
|
"push": push,
|
gac/constants.py
CHANGED
|
@@ -20,8 +20,8 @@ class EnvDefaults:
|
|
|
20
20
|
|
|
21
21
|
MAX_RETRIES: int = 3
|
|
22
22
|
TEMPERATURE: float = 1
|
|
23
|
-
MAX_OUTPUT_TOKENS: int =
|
|
24
|
-
WARNING_LIMIT_TOKENS: int =
|
|
23
|
+
MAX_OUTPUT_TOKENS: int = 4096 # includes reasoning tokens
|
|
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
|
gac/git.py
CHANGED
|
@@ -50,6 +50,48 @@ def get_staged_files(file_type: str | None = None, existing_only: bool = False)
|
|
|
50
50
|
return []
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def get_staged_status() -> str:
|
|
54
|
+
"""Get formatted status of staged files only, excluding unstaged/untracked files.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Formatted status string with M/A/D/R indicators
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
output = run_git_command(["diff", "--name-status", "--staged"])
|
|
61
|
+
if not output:
|
|
62
|
+
return "No changes staged for commit."
|
|
63
|
+
|
|
64
|
+
status_map = {
|
|
65
|
+
"M": "modified",
|
|
66
|
+
"A": "new file",
|
|
67
|
+
"D": "deleted",
|
|
68
|
+
"R": "renamed",
|
|
69
|
+
"C": "copied",
|
|
70
|
+
"T": "typechange",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
status_lines = ["Changes to be committed:"]
|
|
74
|
+
for line in output.splitlines():
|
|
75
|
+
line = line.strip()
|
|
76
|
+
if not line:
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
# Parse status line (e.g., "M\tfile.py" or "R100\told.py\tnew.py")
|
|
80
|
+
parts = line.split("\t")
|
|
81
|
+
if len(parts) < 2:
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
change_type = parts[0][0] # First char is the status (M, A, D, R, etc.)
|
|
85
|
+
file_path = parts[-1] # Last part is the new/current file path
|
|
86
|
+
|
|
87
|
+
status_label = status_map.get(change_type, "modified")
|
|
88
|
+
status_lines.append(f"\t{status_label}: {file_path}")
|
|
89
|
+
|
|
90
|
+
return "\n".join(status_lines)
|
|
91
|
+
except GitError:
|
|
92
|
+
return "No changes staged for commit."
|
|
93
|
+
|
|
94
|
+
|
|
53
95
|
def get_diff(staged: bool = True, color: bool = True, commit1: str | None = None, commit2: str | None = None) -> str:
|
|
54
96
|
"""Get the diff between commits or working tree.
|
|
55
97
|
|
gac/init_cli.py
CHANGED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
import questionary
|
|
7
|
-
from dotenv import set_key
|
|
7
|
+
from dotenv import dotenv_values, set_key
|
|
8
8
|
|
|
9
9
|
from gac.constants import Languages
|
|
10
10
|
|
|
@@ -27,8 +27,12 @@ def _prompt_required_text(prompt: str) -> str | None:
|
|
|
27
27
|
def init() -> None:
|
|
28
28
|
"""Interactively set up $HOME/.gac.env for gac."""
|
|
29
29
|
click.echo("Welcome to gac initialization!\n")
|
|
30
|
+
|
|
31
|
+
# Load existing environment values
|
|
32
|
+
existing_env = {}
|
|
30
33
|
if GAC_ENV_PATH.exists():
|
|
31
34
|
click.echo(f"$HOME/.gac.env already exists at {GAC_ENV_PATH}. Values will be updated.")
|
|
35
|
+
existing_env = dict(dotenv_values(str(GAC_ENV_PATH)))
|
|
32
36
|
else:
|
|
33
37
|
GAC_ENV_PATH.touch()
|
|
34
38
|
click.echo(f"Created $HOME/.gac.env at {GAC_ENV_PATH}.")
|
|
@@ -130,74 +134,183 @@ def init() -> None:
|
|
|
130
134
|
set_key(str(GAC_ENV_PATH), "LMSTUDIO_API_URL", url_to_save)
|
|
131
135
|
click.echo(f"Set LMSTUDIO_API_URL={url_to_save}")
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
if
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
137
|
+
# Determine API key name based on provider
|
|
138
|
+
if is_lmstudio:
|
|
139
|
+
api_key_name = "LMSTUDIO_API_KEY"
|
|
140
|
+
elif is_zai:
|
|
141
|
+
api_key_name = "ZAI_API_KEY"
|
|
142
|
+
elif is_custom_anthropic:
|
|
143
|
+
api_key_name = "CUSTOM_ANTHROPIC_API_KEY"
|
|
144
|
+
elif is_custom_openai:
|
|
145
|
+
api_key_name = "CUSTOM_OPENAI_API_KEY"
|
|
146
|
+
else:
|
|
147
|
+
api_key_name = f"{provider_key.upper()}_API_KEY"
|
|
148
|
+
|
|
149
|
+
# Check if API key already exists
|
|
150
|
+
existing_key = existing_env.get(api_key_name)
|
|
151
|
+
|
|
152
|
+
if existing_key:
|
|
153
|
+
# Key exists - offer options
|
|
154
|
+
click.echo(f"\n{api_key_name} is already configured.")
|
|
155
|
+
action = questionary.select(
|
|
156
|
+
"What would you like to do?",
|
|
157
|
+
choices=[
|
|
158
|
+
"Keep existing key",
|
|
159
|
+
"Enter new key",
|
|
160
|
+
],
|
|
161
|
+
).ask()
|
|
162
|
+
|
|
163
|
+
if action is None:
|
|
164
|
+
click.echo("API key configuration cancelled. Keeping existing key.")
|
|
165
|
+
elif action.startswith("Keep existing"):
|
|
166
|
+
click.echo(f"Keeping existing {api_key_name}")
|
|
167
|
+
elif action.startswith("Enter new"):
|
|
168
|
+
api_key = questionary.password("Enter your new API key (input hidden):").ask()
|
|
169
|
+
if api_key and api_key.strip():
|
|
170
|
+
set_key(str(GAC_ENV_PATH), api_key_name, api_key)
|
|
171
|
+
click.echo(f"Updated {api_key_name} (hidden)")
|
|
172
|
+
else:
|
|
173
|
+
click.echo(f"No key entered. Keeping existing {api_key_name}")
|
|
174
|
+
else:
|
|
175
|
+
# No existing key - prompt for new one
|
|
176
|
+
api_key_prompt = "Enter your API key (input hidden, can be set later):"
|
|
177
|
+
if is_ollama or is_lmstudio:
|
|
178
|
+
click.echo(
|
|
179
|
+
"This provider typically runs locally. API keys are optional unless your instance requires authentication."
|
|
180
|
+
)
|
|
181
|
+
api_key_prompt = "Enter your API key (optional, press Enter to skip):"
|
|
182
|
+
|
|
183
|
+
api_key = questionary.password(api_key_prompt).ask()
|
|
184
|
+
if api_key and api_key.strip():
|
|
185
|
+
set_key(str(GAC_ENV_PATH), api_key_name, api_key)
|
|
186
|
+
click.echo(f"Set {api_key_name} (hidden)")
|
|
187
|
+
elif is_ollama or is_lmstudio:
|
|
188
|
+
click.echo("Skipping API key. You can add one later if needed.")
|
|
150
189
|
else:
|
|
151
|
-
|
|
152
|
-
set_key(str(GAC_ENV_PATH), api_key_name, api_key)
|
|
153
|
-
click.echo(f"Set {api_key_name} (hidden)")
|
|
154
|
-
elif is_ollama or is_lmstudio:
|
|
155
|
-
click.echo("Skipping API key. You can add one later if needed.")
|
|
190
|
+
click.echo("No API key entered. You can add one later by editing ~/.gac.env")
|
|
156
191
|
|
|
157
192
|
# Language selection
|
|
158
193
|
click.echo("\n")
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
click.echo("Language
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
existing_language = existing_env.get("GAC_LANGUAGE")
|
|
195
|
+
|
|
196
|
+
if existing_language:
|
|
197
|
+
# Language already configured - offer options
|
|
198
|
+
existing_translate = existing_env.get("GAC_TRANSLATE_PREFIXES", "false")
|
|
199
|
+
translate_status = "with translated prefixes" if existing_translate == "true" else "with English prefixes"
|
|
200
|
+
click.echo(f"Language is already configured: {existing_language} ({translate_status})")
|
|
201
|
+
|
|
202
|
+
action = questionary.select(
|
|
203
|
+
"What would you like to do?",
|
|
204
|
+
choices=[
|
|
205
|
+
"Keep existing language",
|
|
206
|
+
"Select new language",
|
|
207
|
+
],
|
|
208
|
+
).ask()
|
|
209
|
+
|
|
210
|
+
if action is None or action.startswith("Keep existing"):
|
|
211
|
+
if action is None:
|
|
212
|
+
click.echo("Language configuration cancelled. Keeping existing language.")
|
|
175
213
|
else:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
#
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# Ask about prefix translation
|
|
183
|
-
prefix_choice = questionary.select(
|
|
184
|
-
"How should conventional commit prefixes be handled?",
|
|
185
|
-
choices=[
|
|
186
|
-
"Keep prefixes in English (feat:, fix:, etc.)",
|
|
187
|
-
f"Translate prefixes into {language_value}",
|
|
188
|
-
],
|
|
214
|
+
click.echo(f"Keeping existing language: {existing_language}")
|
|
215
|
+
elif action.startswith("Select new"):
|
|
216
|
+
# Proceed with language selection
|
|
217
|
+
display_names = [lang[0] for lang in Languages.LANGUAGES]
|
|
218
|
+
language_selection = questionary.select(
|
|
219
|
+
"Select a language for commit messages:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
|
|
189
220
|
).ask()
|
|
190
221
|
|
|
191
|
-
if not
|
|
192
|
-
click.echo("
|
|
193
|
-
|
|
222
|
+
if not language_selection:
|
|
223
|
+
click.echo("Language selection cancelled. Keeping existing language.")
|
|
224
|
+
elif language_selection == "English":
|
|
225
|
+
set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", "English")
|
|
226
|
+
set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "false")
|
|
227
|
+
click.echo("Set GAC_LANGUAGE=English")
|
|
228
|
+
click.echo("Set GAC_TRANSLATE_PREFIXES=false")
|
|
194
229
|
else:
|
|
195
|
-
|
|
230
|
+
# Handle custom input
|
|
231
|
+
if language_selection == "Custom":
|
|
232
|
+
custom_language = questionary.text(
|
|
233
|
+
"Enter the language name (e.g., 'Spanish', 'Français', '日本語'):"
|
|
234
|
+
).ask()
|
|
235
|
+
if not custom_language or not custom_language.strip():
|
|
236
|
+
click.echo("No language entered. Keeping existing language.")
|
|
237
|
+
language_value = None
|
|
238
|
+
else:
|
|
239
|
+
language_value = custom_language.strip()
|
|
240
|
+
else:
|
|
241
|
+
# Find the English name for the selected language
|
|
242
|
+
language_value = next(lang[1] for lang in Languages.LANGUAGES if lang[0] == language_selection)
|
|
243
|
+
|
|
244
|
+
if language_value:
|
|
245
|
+
# Ask about prefix translation
|
|
246
|
+
prefix_choice = questionary.select(
|
|
247
|
+
"How should conventional commit prefixes be handled?",
|
|
248
|
+
choices=[
|
|
249
|
+
"Keep prefixes in English (feat:, fix:, etc.)",
|
|
250
|
+
f"Translate prefixes into {language_value}",
|
|
251
|
+
],
|
|
252
|
+
).ask()
|
|
253
|
+
|
|
254
|
+
if not prefix_choice:
|
|
255
|
+
click.echo("Prefix translation selection cancelled. Using English prefixes.")
|
|
256
|
+
translate_prefixes = False
|
|
257
|
+
else:
|
|
258
|
+
translate_prefixes = prefix_choice.startswith("Translate prefixes")
|
|
259
|
+
|
|
260
|
+
# Set the language and prefix translation preference
|
|
261
|
+
set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
|
|
262
|
+
set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
|
|
263
|
+
click.echo(f"Set GAC_LANGUAGE={language_value}")
|
|
264
|
+
click.echo(f"Set GAC_TRANSLATE_PREFIXES={'true' if translate_prefixes else 'false'}")
|
|
265
|
+
else:
|
|
266
|
+
# No existing language - proceed with normal flow
|
|
267
|
+
display_names = [lang[0] for lang in Languages.LANGUAGES]
|
|
268
|
+
language_selection = questionary.select(
|
|
269
|
+
"Select a language for commit messages:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
|
|
270
|
+
).ask()
|
|
271
|
+
|
|
272
|
+
if not language_selection:
|
|
273
|
+
click.echo("Language selection cancelled. Using English (default).")
|
|
274
|
+
elif language_selection == "English":
|
|
275
|
+
set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", "English")
|
|
276
|
+
set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "false")
|
|
277
|
+
click.echo("Set GAC_LANGUAGE=English")
|
|
278
|
+
click.echo("Set GAC_TRANSLATE_PREFIXES=false")
|
|
279
|
+
else:
|
|
280
|
+
# Handle custom input
|
|
281
|
+
if language_selection == "Custom":
|
|
282
|
+
custom_language = questionary.text(
|
|
283
|
+
"Enter the language name (e.g., 'Spanish', 'Français', '日本語'):"
|
|
284
|
+
).ask()
|
|
285
|
+
if not custom_language or not custom_language.strip():
|
|
286
|
+
click.echo("No language entered. Using English (default).")
|
|
287
|
+
language_value = None
|
|
288
|
+
else:
|
|
289
|
+
language_value = custom_language.strip()
|
|
290
|
+
else:
|
|
291
|
+
# Find the English name for the selected language
|
|
292
|
+
language_value = next(lang[1] for lang in Languages.LANGUAGES if lang[0] == language_selection)
|
|
293
|
+
|
|
294
|
+
if language_value:
|
|
295
|
+
# Ask about prefix translation
|
|
296
|
+
prefix_choice = questionary.select(
|
|
297
|
+
"How should conventional commit prefixes be handled?",
|
|
298
|
+
choices=[
|
|
299
|
+
"Keep prefixes in English (feat:, fix:, etc.)",
|
|
300
|
+
f"Translate prefixes into {language_value}",
|
|
301
|
+
],
|
|
302
|
+
).ask()
|
|
303
|
+
|
|
304
|
+
if not prefix_choice:
|
|
305
|
+
click.echo("Prefix translation selection cancelled. Using English prefixes.")
|
|
306
|
+
translate_prefixes = False
|
|
307
|
+
else:
|
|
308
|
+
translate_prefixes = prefix_choice.startswith("Translate prefixes")
|
|
196
309
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
310
|
+
# Set the language and prefix translation preference
|
|
311
|
+
set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
|
|
312
|
+
set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
|
|
313
|
+
click.echo(f"Set GAC_LANGUAGE={language_value}")
|
|
314
|
+
click.echo(f"Set GAC_TRANSLATE_PREFIXES={'true' if translate_prefixes else 'false'}")
|
|
202
315
|
|
|
203
316
|
click.echo(f"\ngac environment setup complete. You can edit {GAC_ENV_PATH} to update values later.")
|
gac/language_cli.py
CHANGED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
import questionary
|
|
7
|
-
from dotenv import set_key
|
|
7
|
+
from dotenv import set_key
|
|
8
8
|
|
|
9
9
|
from gac.constants import Languages
|
|
10
10
|
|
|
@@ -30,14 +30,14 @@ def language() -> None:
|
|
|
30
30
|
GAC_ENV_PATH.touch()
|
|
31
31
|
click.echo(f"Created {GAC_ENV_PATH}")
|
|
32
32
|
|
|
33
|
-
# Handle English
|
|
33
|
+
# Handle English - set explicitly
|
|
34
34
|
if selection == "English":
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", "English")
|
|
36
|
+
set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "false")
|
|
37
|
+
click.echo("✓ Set language to English")
|
|
38
|
+
click.echo(" GAC_LANGUAGE=English")
|
|
39
|
+
click.echo(" GAC_TRANSLATE_PREFIXES=false")
|
|
40
|
+
click.echo(f"\n Configuration saved to {GAC_ENV_PATH}")
|
|
41
41
|
return
|
|
42
42
|
|
|
43
43
|
# Handle custom input
|