gac 2.2.0__py3-none-any.whl → 2.4.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/init_cli.py CHANGED
@@ -1,16 +1,58 @@
1
1
  """CLI for initializing gac configuration interactively."""
2
2
 
3
+ import os
3
4
  from pathlib import Path
4
5
 
5
6
  import click
6
7
  import questionary
7
- from dotenv import set_key
8
+ from dotenv import dotenv_values, load_dotenv, set_key
8
9
 
9
10
  from gac.constants import Languages
10
11
 
11
12
  GAC_ENV_PATH = Path.home() / ".gac.env"
12
13
 
13
14
 
15
+ def _should_show_rtl_warning_for_init() -> bool:
16
+ """Check if RTL warning should be shown based on init's GAC_ENV_PATH.
17
+
18
+ Returns:
19
+ True if warning should be shown, False if user previously confirmed
20
+ """
21
+ if GAC_ENV_PATH.exists():
22
+ load_dotenv(GAC_ENV_PATH)
23
+ rtl_confirmed = os.getenv("GAC_RTL_CONFIRMED", "false").lower() in ("true", "1", "yes", "on")
24
+ return not rtl_confirmed
25
+ return True # Show warning if no config exists
26
+
27
+
28
+ def _show_rtl_warning_for_init(language_name: str) -> bool:
29
+ """Show RTL language warning for init command and save preference to GAC_ENV_PATH.
30
+
31
+ Args:
32
+ language_name: Name of the RTL language
33
+
34
+ Returns:
35
+ True if user wants to proceed, False if they cancel
36
+ """
37
+
38
+ terminal_width = 80 # Use default width
39
+ title = "⚠️ RTL Language Detected".center(terminal_width)
40
+
41
+ click.echo()
42
+ click.echo(click.style(title, fg="yellow", bold=True))
43
+ click.echo()
44
+ click.echo("Right-to-left (RTL) languages may not display correctly in gac due to terminal limitations.")
45
+ click.echo("However, the commit messages will work fine and should be readable in Git clients")
46
+ click.echo("that properly support RTL text (like most web interfaces and modern tools).\n")
47
+
48
+ proceed = questionary.confirm("Do you want to proceed anyway?").ask()
49
+ if proceed:
50
+ # Remember that user has confirmed RTL acceptance
51
+ set_key(str(GAC_ENV_PATH), "GAC_RTL_CONFIRMED", "true")
52
+ click.echo("✓ RTL preference saved - you won't see this warning again")
53
+ return proceed if proceed is not None else False
54
+
55
+
14
56
  def _prompt_required_text(prompt: str) -> str | None:
15
57
  """Prompt until a non-empty string is provided or the user cancels."""
16
58
  while True:
@@ -23,16 +65,20 @@ def _prompt_required_text(prompt: str) -> str | None:
23
65
  click.echo("A value is required. Please try again.")
24
66
 
25
67
 
26
- @click.command()
27
- def init() -> None:
28
- """Interactively set up $HOME/.gac.env for gac."""
29
- click.echo("Welcome to gac initialization!\n")
68
+ def _load_existing_env() -> dict[str, str]:
69
+ """Ensure the env file exists and return its current values."""
70
+ existing_env: dict[str, str] = {}
30
71
  if GAC_ENV_PATH.exists():
31
72
  click.echo(f"$HOME/.gac.env already exists at {GAC_ENV_PATH}. Values will be updated.")
73
+ existing_env = {k: v for k, v in dotenv_values(str(GAC_ENV_PATH)).items() if v is not None}
32
74
  else:
33
75
  GAC_ENV_PATH.touch()
34
76
  click.echo(f"Created $HOME/.gac.env at {GAC_ENV_PATH}.")
77
+ return existing_env
78
+
35
79
 
80
+ def _configure_model(existing_env: dict[str, str]) -> bool:
81
+ """Run the provider/model/API key configuration flow."""
36
82
  providers = [
37
83
  ("Anthropic", "claude-haiku-4-5"),
38
84
  ("Cerebras", "zai-glm-4.6"),
@@ -59,7 +105,7 @@ def init() -> None:
59
105
  provider = questionary.select("Select your provider:", choices=provider_names).ask()
60
106
  if not provider:
61
107
  click.echo("Provider selection cancelled. Exiting.")
62
- return
108
+ return False
63
109
  provider_key = provider.lower().replace(".", "").replace(" ", "-").replace("(", "").replace(")", "")
64
110
 
65
111
  is_ollama = provider_key == "ollama"
@@ -73,7 +119,7 @@ def init() -> None:
73
119
  endpoint_id = _prompt_required_text("Enter the Streamlake inference endpoint ID (required):")
74
120
  if endpoint_id is None:
75
121
  click.echo("Streamlake configuration cancelled. Exiting.")
76
- return
122
+ return False
77
123
  model_to_save = endpoint_id
78
124
  else:
79
125
  model_suggestion = dict(providers)[provider]
@@ -84,7 +130,7 @@ def init() -> None:
84
130
  model = questionary.text(model_prompt, default=model_suggestion).ask()
85
131
  if model is None:
86
132
  click.echo("Model entry cancelled. Exiting.")
87
- return
133
+ return False
88
134
  model_to_save = model.strip() if model.strip() else model_suggestion
89
135
 
90
136
  set_key(str(GAC_ENV_PATH), "GAC_MODEL", f"{provider_key}:{model_to_save}")
@@ -94,7 +140,7 @@ def init() -> None:
94
140
  base_url = _prompt_required_text("Enter the custom Anthropic-compatible base URL (required):")
95
141
  if base_url is None:
96
142
  click.echo("Custom Anthropic base URL entry cancelled. Exiting.")
97
- return
143
+ return False
98
144
  set_key(str(GAC_ENV_PATH), "CUSTOM_ANTHROPIC_BASE_URL", base_url)
99
145
  click.echo(f"Set CUSTOM_ANTHROPIC_BASE_URL={base_url}")
100
146
 
@@ -108,7 +154,7 @@ def init() -> None:
108
154
  base_url = _prompt_required_text("Enter the custom OpenAI-compatible base URL (required):")
109
155
  if base_url is None:
110
156
  click.echo("Custom OpenAI base URL entry cancelled. Exiting.")
111
- return
157
+ return False
112
158
  set_key(str(GAC_ENV_PATH), "CUSTOM_OPENAI_BASE_URL", base_url)
113
159
  click.echo(f"Set CUSTOM_OPENAI_BASE_URL={base_url}")
114
160
  elif is_ollama:
@@ -116,7 +162,7 @@ def init() -> None:
116
162
  url = questionary.text(f"Enter the Ollama API URL (default: {url_default}):", default=url_default).ask()
117
163
  if url is None:
118
164
  click.echo("Ollama URL entry cancelled. Exiting.")
119
- return
165
+ return False
120
166
  url_to_save = url.strip() if url.strip() else url_default
121
167
  set_key(str(GAC_ENV_PATH), "OLLAMA_API_URL", url_to_save)
122
168
  click.echo(f"Set OLLAMA_API_URL={url_to_save}")
@@ -125,79 +171,265 @@ def init() -> None:
125
171
  url = questionary.text(f"Enter the LM Studio API URL (default: {url_default}):", default=url_default).ask()
126
172
  if url is None:
127
173
  click.echo("LM Studio URL entry cancelled. Exiting.")
128
- return
174
+ return False
129
175
  url_to_save = url.strip() if url.strip() else url_default
130
176
  set_key(str(GAC_ENV_PATH), "LMSTUDIO_API_URL", url_to_save)
131
177
  click.echo(f"Set LMSTUDIO_API_URL={url_to_save}")
132
178
 
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"
179
+ # Determine API key name based on provider
180
+ if is_lmstudio:
181
+ api_key_name = "LMSTUDIO_API_KEY"
182
+ elif is_zai:
183
+ api_key_name = "ZAI_API_KEY"
184
+ elif is_custom_anthropic:
185
+ api_key_name = "CUSTOM_ANTHROPIC_API_KEY"
186
+ elif is_custom_openai:
187
+ api_key_name = "CUSTOM_OPENAI_API_KEY"
188
+ else:
189
+ api_key_name = f"{provider_key.upper()}_API_KEY"
190
+
191
+ # Check if API key already exists
192
+ existing_key = existing_env.get(api_key_name)
193
+
194
+ if existing_key:
195
+ # Key exists - offer options
196
+ click.echo(f"\n{api_key_name} is already configured.")
197
+ action = questionary.select(
198
+ "What would you like to do?",
199
+ choices=[
200
+ "Keep existing key",
201
+ "Enter new key",
202
+ ],
203
+ ).ask()
204
+
205
+ if action is None:
206
+ click.echo("API key configuration cancelled. Keeping existing key.")
207
+ elif action.startswith("Keep existing"):
208
+ click.echo(f"Keeping existing {api_key_name}")
209
+ elif action.startswith("Enter new"):
210
+ api_key = questionary.password("Enter your new API key (input hidden):").ask()
211
+ if api_key and api_key.strip():
212
+ set_key(str(GAC_ENV_PATH), api_key_name, api_key)
213
+ click.echo(f"Updated {api_key_name} (hidden)")
214
+ else:
215
+ click.echo(f"No key entered. Keeping existing {api_key_name}")
216
+ else:
217
+ # No existing key - prompt for new one
218
+ api_key_prompt = "Enter your API key (input hidden, can be set later):"
219
+ if is_ollama or is_lmstudio:
220
+ click.echo(
221
+ "This provider typically runs locally. API keys are optional unless your instance requires authentication."
222
+ )
223
+ api_key_prompt = "Enter your API key (optional, press Enter to skip):"
224
+
225
+ api_key = questionary.password(api_key_prompt).ask()
226
+ if api_key and api_key.strip():
227
+ set_key(str(GAC_ENV_PATH), api_key_name, api_key)
228
+ click.echo(f"Set {api_key_name} (hidden)")
229
+ elif is_ollama or is_lmstudio:
230
+ click.echo("Skipping API key. You can add one later if needed.")
150
231
  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.")
232
+ click.echo("No API key entered. You can add one later by editing ~/.gac.env")
233
+
234
+ return True
235
+
236
+
237
+ def _configure_language(existing_env: dict[str, str]) -> None:
238
+ """Run the language configuration flow."""
239
+ from gac.language_cli import is_rtl_text
156
240
 
157
- # Language selection
158
241
  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
242
+ existing_language = existing_env.get("GAC_LANGUAGE")
243
+
244
+ if existing_language:
245
+ # Language already configured - offer options
246
+ existing_translate = existing_env.get("GAC_TRANSLATE_PREFIXES", "false")
247
+ translate_status = "with translated prefixes" if existing_translate == "true" else "with English prefixes"
248
+ click.echo(f"Language is already configured: {existing_language} ({translate_status})")
249
+
250
+ action = questionary.select(
251
+ "What would you like to do?",
252
+ choices=[
253
+ "Keep existing language",
254
+ "Select new language",
255
+ ],
256
+ ).ask()
257
+
258
+ if action is None or action.startswith("Keep existing"):
259
+ if action is None:
260
+ click.echo("Language configuration cancelled. Keeping existing language.")
175
261
  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
- ],
262
+ click.echo(f"Keeping existing language: {existing_language}")
263
+ elif action.startswith("Select new"):
264
+ # Proceed with language selection
265
+ display_names = [lang[0] for lang in Languages.LANGUAGES]
266
+ language_selection = questionary.select(
267
+ "Select a language for commit messages:",
268
+ choices=display_names,
269
+ use_shortcuts=True,
270
+ use_arrow_keys=True,
271
+ use_jk_keys=False,
189
272
  ).ask()
190
273
 
191
- if not prefix_choice:
192
- click.echo("Prefix translation selection cancelled. Using English prefixes.")
193
- translate_prefixes = False
274
+ if not language_selection:
275
+ click.echo("Language selection cancelled. Keeping existing language.")
276
+ elif language_selection == "English":
277
+ set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", "English")
278
+ set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "false")
279
+ click.echo("Set GAC_LANGUAGE=English")
280
+ click.echo("Set GAC_TRANSLATE_PREFIXES=false")
281
+ else:
282
+ # Handle custom input
283
+ if language_selection == "Custom":
284
+ custom_language = questionary.text(
285
+ "Enter the language name (e.g., 'Spanish', 'Français', '日本語'):"
286
+ ).ask()
287
+ if not custom_language or not custom_language.strip():
288
+ click.echo("No language entered. Keeping existing language.")
289
+ language_value = None
290
+ else:
291
+ language_value = custom_language.strip()
292
+
293
+ # Check if the custom language appears to be RTL
294
+ if is_rtl_text(language_value):
295
+ if not _should_show_rtl_warning_for_init():
296
+ click.echo(
297
+ f"\nℹ️ Using RTL language {language_value} (RTL warning previously confirmed)"
298
+ )
299
+ else:
300
+ if not _show_rtl_warning_for_init(language_value):
301
+ click.echo("Language selection cancelled. Keeping existing language.")
302
+ language_value = None
303
+ else:
304
+ # Find the English name for the selected language
305
+ language_value = next(lang[1] for lang in Languages.LANGUAGES if lang[0] == language_selection)
306
+
307
+ # Check if predefined language is RTL
308
+ if is_rtl_text(language_value):
309
+ if not _should_show_rtl_warning_for_init():
310
+ click.echo(f"\nℹ️ Using RTL language {language_value} (RTL warning previously confirmed)")
311
+ else:
312
+ if not _show_rtl_warning_for_init(language_value):
313
+ click.echo("Language selection cancelled. Keeping existing language.")
314
+ language_value = None
315
+
316
+ if language_value:
317
+ # Ask about prefix translation
318
+ prefix_choice = questionary.select(
319
+ "How should conventional commit prefixes be handled?",
320
+ choices=[
321
+ "Keep prefixes in English (feat:, fix:, etc.)",
322
+ f"Translate prefixes into {language_value}",
323
+ ],
324
+ ).ask()
325
+
326
+ if not prefix_choice:
327
+ click.echo("Prefix translation selection cancelled. Using English prefixes.")
328
+ translate_prefixes = False
329
+ else:
330
+ translate_prefixes = prefix_choice.startswith("Translate prefixes")
331
+
332
+ # Set the language and prefix translation preference
333
+ set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
334
+ set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
335
+ click.echo(f"Set GAC_LANGUAGE={language_value}")
336
+ click.echo(f"Set GAC_TRANSLATE_PREFIXES={'true' if translate_prefixes else 'false'}")
337
+ else:
338
+ # No existing language - proceed with normal flow
339
+ display_names = [lang[0] for lang in Languages.LANGUAGES]
340
+ language_selection = questionary.select(
341
+ "Select a language for commit messages:",
342
+ choices=display_names,
343
+ use_shortcuts=True,
344
+ use_arrow_keys=True,
345
+ use_jk_keys=False,
346
+ ).ask()
347
+
348
+ if not language_selection:
349
+ click.echo("Language selection cancelled. Using English (default).")
350
+ elif language_selection == "English":
351
+ set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", "English")
352
+ set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "false")
353
+ click.echo("Set GAC_LANGUAGE=English")
354
+ click.echo("Set GAC_TRANSLATE_PREFIXES=false")
355
+ else:
356
+ # Handle custom input
357
+ if language_selection == "Custom":
358
+ custom_language = questionary.text(
359
+ "Enter the language name (e.g., 'Spanish', 'Français', '日本語'):"
360
+ ).ask()
361
+ if not custom_language or not custom_language.strip():
362
+ click.echo("No language entered. Using English (default).")
363
+ language_value = None
364
+ else:
365
+ language_value = custom_language.strip()
366
+
367
+ # Check if the custom language appears to be RTL
368
+ if is_rtl_text(language_value):
369
+ if not _should_show_rtl_warning_for_init():
370
+ click.echo(f"\nℹ️ Using RTL language {language_value} (RTL warning previously confirmed)")
371
+ else:
372
+ if not _show_rtl_warning_for_init(language_value):
373
+ click.echo("Language selection cancelled. Using English (default).")
374
+ language_value = None
194
375
  else:
195
- translate_prefixes = prefix_choice.startswith("Translate prefixes")
376
+ # Find the English name for the selected language
377
+ language_value = next(lang[1] for lang in Languages.LANGUAGES if lang[0] == language_selection)
378
+
379
+ # Check if predefined language is RTL
380
+ if is_rtl_text(language_value):
381
+ if not _should_show_rtl_warning_for_init():
382
+ click.echo(f"\nℹ️ Using RTL language {language_value} (RTL warning previously confirmed)")
383
+ else:
384
+ if not _show_rtl_warning_for_init(language_value):
385
+ click.echo("Language selection cancelled. Using English (default).")
386
+ language_value = None
387
+
388
+ if language_value:
389
+ # Ask about prefix translation
390
+ prefix_choice = questionary.select(
391
+ "How should conventional commit prefixes be handled?",
392
+ choices=[
393
+ "Keep prefixes in English (feat:, fix:, etc.)",
394
+ f"Translate prefixes into {language_value}",
395
+ ],
396
+ ).ask()
196
397
 
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'}")
398
+ if not prefix_choice:
399
+ click.echo("Prefix translation selection cancelled. Using English prefixes.")
400
+ translate_prefixes = False
401
+ else:
402
+ translate_prefixes = prefix_choice.startswith("Translate prefixes")
403
+
404
+ # Set the language and prefix translation preference
405
+ set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
406
+ set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
407
+ click.echo(f"Set GAC_LANGUAGE={language_value}")
408
+ click.echo(f"Set GAC_TRANSLATE_PREFIXES={'true' if translate_prefixes else 'false'}")
409
+
410
+ return
411
+
412
+
413
+ @click.command()
414
+ def init() -> None:
415
+ """Interactively set up $HOME/.gac.env for gac."""
416
+ click.echo("Welcome to gac initialization!\n")
417
+
418
+ existing_env = _load_existing_env()
419
+ if not _configure_model(existing_env):
420
+ return
421
+ _configure_language(existing_env)
202
422
 
203
423
  click.echo(f"\ngac environment setup complete. You can edit {GAC_ENV_PATH} to update values later.")
424
+
425
+
426
+ @click.command()
427
+ def model() -> None:
428
+ """Interactively update provider/model/API key without language prompts."""
429
+ click.echo("Welcome to gac model configuration!\n")
430
+
431
+ existing_env = _load_existing_env()
432
+ if not _configure_model(existing_env):
433
+ return
434
+
435
+ click.echo(f"\nModel configuration complete. You can edit {GAC_ENV_PATH} to update values later.")