gac 2.2.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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information for gac package."""
2
2
 
3
- __version__ = "2.2.0"
3
+ __version__ = "2.3.0"
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 commit message with {provider} {model_name}...", spinner="dots")
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
- spinner.succeed(f"Generated commit message with {provider} {model_name}")
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 commit message with {provider} {model_name}")
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
- logger.warning(f"AI generation failed (attempt {attempt + 1}), retrying in {wait_time}s: {str(e)}")
177
-
178
- if spinner:
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
- logger.error(f"AI generation failed after {max_retries} attempts: {str(e)}")
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 commit message with {provider} {model_name}")
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
- error_message = f"Failed to generate commit message after {max_retries} attempts"
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,7 +20,7 @@ class EnvDefaults:
20
20
 
21
21
  MAX_RETRIES: int = 3
22
22
  TEMPERATURE: float = 1
23
- MAX_OUTPUT_TOKENS: int = 1024 # includes reasoning tokens
23
+ MAX_OUTPUT_TOKENS: int = 4096 # includes reasoning tokens
24
24
  WARNING_LIMIT_TOKENS: int = 32768
25
25
  ALWAYS_INCLUDE_SCOPE: bool = False
26
26
  SKIP_SECRET_SCAN: 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
- api_key_prompt = "Enter your API key (input hidden, can be set later):"
134
- if is_ollama or is_lmstudio:
135
- click.echo(
136
- "This provider typically runs locally. API keys are optional unless your instance requires authentication."
137
- )
138
- api_key_prompt = "Enter your API key (optional, press Enter to skip):"
139
-
140
- api_key = questionary.password(api_key_prompt).ask()
141
- if api_key:
142
- if is_lmstudio:
143
- api_key_name = "LMSTUDIO_API_KEY"
144
- elif is_zai:
145
- api_key_name = "ZAI_API_KEY"
146
- elif is_custom_anthropic:
147
- api_key_name = "CUSTOM_ANTHROPIC_API_KEY"
148
- elif is_custom_openai:
149
- api_key_name = "CUSTOM_OPENAI_API_KEY"
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
- api_key_name = f"{provider_key.upper()}_API_KEY"
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
- display_names = [lang[0] for lang in Languages.LANGUAGES]
160
- language_selection = questionary.select(
161
- "Select a language for commit messages:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
162
- ).ask()
163
-
164
- if not language_selection:
165
- click.echo("Language selection cancelled. Using English (default).")
166
- elif language_selection == "English":
167
- click.echo("Set language to English (default)")
168
- else:
169
- # Handle custom input
170
- if language_selection == "Custom":
171
- custom_language = questionary.text("Enter the language name (e.g., 'Spanish', 'Français', '日本語'):").ask()
172
- if not custom_language or not custom_language.strip():
173
- click.echo("No language entered. Using English (default).")
174
- language_value = None
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
- language_value = custom_language.strip()
177
- else:
178
- # Find the English name for the selected language
179
- language_value = next(lang[1] for lang in Languages.LANGUAGES if lang[0] == language_selection)
180
-
181
- if language_value:
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 prefix_choice:
192
- click.echo("Prefix translation selection cancelled. Using English prefixes.")
193
- translate_prefixes = False
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
- translate_prefixes = prefix_choice.startswith("Translate prefixes")
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
- # Set the language and prefix translation preference
198
- set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
199
- set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
200
- click.echo(f"Set GAC_LANGUAGE={language_value}")
201
- click.echo(f"Set GAC_TRANSLATE_PREFIXES={'true' if translate_prefixes else 'false'}")
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, unset_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 (default) - remove the setting
33
+ # Handle English - set explicitly
34
34
  if selection == "English":
35
- try:
36
- unset_key(str(GAC_ENV_PATH), "GAC_LANGUAGE")
37
- click.echo("✓ Set language to English (default)")
38
- click.echo(f" Removed GAC_LANGUAGE from {GAC_ENV_PATH}")
39
- except Exception:
40
- click.echo(" Set language to English (default)")
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