git-copilot-commit 0.6.0__py3-none-any.whl → 0.7.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.
git_copilot_commit/cli.py CHANGED
@@ -9,12 +9,10 @@ import re
9
9
  import sys
10
10
  from typing import Annotated, Sequence
11
11
 
12
- import rich
13
- import typer
12
+ import cyclopts
14
13
  from rich.console import Console
15
14
  from rich.panel import Panel
16
- from rich.prompt import Confirm
17
- from typer.main import get_command
15
+ from rich.prompt import Confirm, Prompt
18
16
 
19
17
  from .git import GitRepository, GitError, GitStatus, NotAGitRepositoryError
20
18
  from .split_commits import (
@@ -34,7 +32,7 @@ from .llms import core as llm
34
32
  from .llms import providers
35
33
 
36
34
  console = Console()
37
- app = typer.Typer(help=__doc__, add_completion=False)
35
+ app = cyclopts.App(help=__doc__, version=lambda: f"git-copilot-commit {__version__}")
38
36
 
39
37
  COMMIT_MESSAGE_PROMPT_FILENAME = "commit-message-generator-prompt.md"
40
38
  SPLIT_COMMIT_PLANNER_PROMPT_FILENAME = "split-commit-planner-prompt.md"
@@ -57,38 +55,41 @@ NATIVE_TLS_HELP = (
57
55
 
58
56
  CaBundleOption = Annotated[
59
57
  str | None,
60
- typer.Option("--ca-bundle", metavar="PATH", help=CA_BUNDLE_HELP),
58
+ cyclopts.Parameter(name="--ca-bundle", help=CA_BUNDLE_HELP),
61
59
  ]
62
60
  InsecureOption = Annotated[
63
61
  bool,
64
- typer.Option("--insecure", help="Disable SSL certificate verification."),
62
+ cyclopts.Parameter(name="--insecure", help="Disable SSL certificate verification."),
65
63
  ]
66
64
  NativeTlsOption = Annotated[
67
65
  bool,
68
- typer.Option("--native-tls/--no-native-tls", help=NATIVE_TLS_HELP),
66
+ cyclopts.Parameter(
67
+ name="--native-tls",
68
+ negative="--no-native-tls",
69
+ help=NATIVE_TLS_HELP,
70
+ ),
69
71
  ]
70
72
  ProviderOption = Annotated[
71
73
  str | None,
72
- typer.Option(
73
- "--provider",
74
+ cyclopts.Parameter(
75
+ name="--provider",
74
76
  help="LLM provider to use: copilot or openai.",
75
77
  ),
76
78
  ]
77
79
  BaseUrlOption = Annotated[
78
80
  str | None,
79
- typer.Option(
80
- "--base-url",
81
- metavar="URL",
81
+ cyclopts.Parameter(
82
+ name="--base-url",
82
83
  help=(
83
- "Base URL for an OpenAI-compatible provider, for example "
84
- "http://127.0.0.1:11434/v1."
84
+ "Endpoint URL for an OpenAI-compatible provider, for example "
85
+ "http://127.0.0.1:11434/v1/chat/completions."
85
86
  ),
86
87
  ),
87
88
  ]
88
89
  ApiKeyOption = Annotated[
89
90
  str | None,
90
- typer.Option(
91
- "--api-key",
91
+ cyclopts.Parameter(
92
+ name="--api-key",
92
93
  help="API key for an OpenAI-compatible provider. Omit when the server does not require one.",
93
94
  ),
94
95
  ]
@@ -96,8 +97,8 @@ ApiKeyOption = Annotated[
96
97
 
97
98
  SplitOption = Annotated[
98
99
  bool,
99
- typer.Option(
100
- "--split",
100
+ cyclopts.Parameter(
101
+ name="--split",
101
102
  help=(
102
103
  "Split staged hunks into multiple commits automatically. Pass "
103
104
  "`--split=N` to express a preference for N commits."
@@ -106,10 +107,10 @@ SplitOption = Annotated[
106
107
  ]
107
108
  SplitCountOption = Annotated[
108
109
  int | None,
109
- typer.Option(
110
- "--split-count",
111
- hidden=True,
112
- min=1,
110
+ cyclopts.Parameter(
111
+ name="--split-count",
112
+ show=False,
113
+ validator=cyclopts.validators.Number(gte=1),
113
114
  ),
114
115
  ]
115
116
 
@@ -225,39 +226,13 @@ def order_prepared_split_commits(
225
226
  def run(args: Sequence[str] | None = None) -> None:
226
227
  """Run the CLI entrypoint with argument normalization."""
227
228
  raw_args = list(args) if args is not None else sys.argv[1:]
228
- command = get_command(app)
229
- command.main(
230
- args=preprocess_cli_args(raw_args),
231
- prog_name=Path(sys.argv[0]).name,
232
- )
229
+ app(preprocess_cli_args(raw_args))
233
230
 
234
231
 
235
- def version_callback(value: bool):
236
- if value:
237
- rich.print(f"git-copilot-commit [bold yellow]{__version__}[/]")
238
- raise typer.Exit()
239
-
240
-
241
- @app.callback(invoke_without_command=True)
242
- def main(
243
- ctx: typer.Context,
244
- _: bool = typer.Option(
245
- False, "--version", callback=version_callback, help="Show version and exit"
246
- ),
247
- ):
248
- """
249
- Automatically commit changes in the current git repository.
250
- """
251
- if ctx.invoked_subcommand is None:
252
- # Show help when no command is provided
253
- console.print(ctx.get_help())
254
- raise typer.Exit()
255
- else:
256
- # Don't show version for print command to avoid interfering with pipes
257
- if ctx.invoked_subcommand != "echo":
258
- console.print(
259
- f"[bold]{(__package__ or 'git_copilot_commit').replace('_', '-')}[/] - [bold green]v{__version__}[/]\n"
260
- )
232
+ def print_cli_banner() -> None:
233
+ """Render the CLI banner before command output."""
234
+ package_name = (__package__ or "git_copilot_commit").replace("_", "-")
235
+ console.print(f"[bold]{package_name}[/] - [bold green]v{__version__}[/]\n")
261
236
 
262
237
 
263
238
  def get_prompt_locations(filename: str):
@@ -280,7 +255,7 @@ def resolve_prompt_file() -> Path | None:
280
255
  console.print(
281
256
  f"[red]Configured default prompt file in {settings.config_file} is invalid.[/red]"
282
257
  )
283
- raise typer.Exit(1)
258
+ raise SystemExit(1)
284
259
 
285
260
  if configured_prompt_file is None:
286
261
  return None
@@ -298,7 +273,7 @@ def load_system_prompt() -> str:
298
273
  console.print(
299
274
  f"[red]Error reading prompt file {resolved_prompt_file}: {exc}[/red]"
300
275
  )
301
- raise typer.Exit(1)
276
+ raise SystemExit(1)
302
277
 
303
278
  return load_named_prompt(COMMIT_MESSAGE_PROMPT_FILENAME)
304
279
 
@@ -312,7 +287,7 @@ def load_named_prompt(filename: str) -> str:
312
287
  continue
313
288
 
314
289
  console.print(f"[red]Error: Prompt file {filename} not found in any location[/red]")
315
- raise typer.Exit(1)
290
+ raise SystemExit(1)
316
291
 
317
292
 
318
293
  def build_http_client_config(
@@ -357,7 +332,7 @@ def build_commit_message_prompt(
357
332
  """Build the prompt used to generate a commit message."""
358
333
  if not status.has_staged_changes:
359
334
  console.print("[red]No staged changes to commit.[/red]")
360
- raise typer.Exit()
335
+ raise SystemExit()
361
336
 
362
337
  prompt_parts = [
363
338
  "`git status`:\n",
@@ -383,7 +358,6 @@ def normalize_model_name(model: str | None) -> str | None:
383
358
  if model is not None:
384
359
  for prefix in (
385
360
  "copilot/",
386
- "openai/",
387
361
  "openai-compatible/",
388
362
  ):
389
363
  if model.startswith(prefix):
@@ -397,6 +371,8 @@ def ask_llm_with_system_prompt(
397
371
  model: str | None = None,
398
372
  provider_config: providers.ProviderConfig | None = None,
399
373
  http_client_config: llm.HttpClientConfig | None = None,
374
+ disable_thinking: bool = True,
375
+ max_tokens: int = 1024,
400
376
  ) -> str:
401
377
  """Send a prepared prompt to the selected LLM provider."""
402
378
  return providers.ask(
@@ -412,6 +388,8 @@ def ask_llm_with_system_prompt(
412
388
  provider_config=provider_config,
413
389
  model=normalize_model_name(model),
414
390
  http_client_config=http_client_config,
391
+ disable_thinking=disable_thinking,
392
+ max_tokens=max_tokens,
415
393
  )
416
394
 
417
395
 
@@ -420,6 +398,8 @@ def generate_commit_message_for_prompt(
420
398
  model: str | None = None,
421
399
  provider_config: providers.ProviderConfig | None = None,
422
400
  http_client_config: llm.HttpClientConfig | None = None,
401
+ disable_thinking: bool = True,
402
+ max_tokens: int = 1024,
423
403
  ) -> str:
424
404
  """Generate a conventional commit message from a prepared prompt."""
425
405
  return ask_llm_with_system_prompt(
@@ -428,6 +408,8 @@ def generate_commit_message_for_prompt(
428
408
  model=model,
429
409
  provider_config=provider_config,
430
410
  http_client_config=http_client_config,
411
+ disable_thinking=disable_thinking,
412
+ max_tokens=max_tokens,
431
413
  )
432
414
 
433
415
 
@@ -461,6 +443,8 @@ def generate_commit_message_for_status(
461
443
  context: str = "",
462
444
  provider_config: providers.ProviderConfig | None = None,
463
445
  http_client_config: llm.HttpClientConfig | None = None,
446
+ disable_thinking: bool = True,
447
+ max_tokens: int = 1024,
464
448
  ) -> str:
465
449
  """Generate a commit message for a staged status snapshot."""
466
450
  full_prompt = build_commit_message_prompt(status, context=context)
@@ -470,6 +454,8 @@ def generate_commit_message_for_status(
470
454
  model=model,
471
455
  provider_config=provider_config,
472
456
  http_client_config=http_client_config,
457
+ disable_thinking=disable_thinking,
458
+ max_tokens=max_tokens,
473
459
  )
474
460
  except llm.LLMError as exc:
475
461
  if not should_retry_with_compact_prompt(exc):
@@ -488,6 +474,8 @@ def generate_commit_message_for_status(
488
474
  model=model,
489
475
  provider_config=provider_config,
490
476
  http_client_config=http_client_config,
477
+ disable_thinking=disable_thinking,
478
+ max_tokens=max_tokens,
491
479
  )
492
480
 
493
481
 
@@ -506,13 +494,13 @@ def commit_with_retry_no_verify(
506
494
  "Retry commit with [bold]`-n`[/] (skip hooks) using the same commit message?",
507
495
  default=True,
508
496
  ):
509
- raise typer.Exit(1)
497
+ raise SystemExit(1)
510
498
 
511
499
  try:
512
500
  return repo.commit(message, use_editor=use_editor, no_verify=True, env=env)
513
501
  except GitError as retry_error:
514
502
  console.print(f"[red]Commit with -n failed: {retry_error}[/red]")
515
- raise typer.Exit(1)
503
+ raise SystemExit(1)
516
504
 
517
505
 
518
506
  def ensure_copilot_authentication(
@@ -534,7 +522,7 @@ def ensure_copilot_authentication(
534
522
  )
535
523
  except copilot.LLMError as exc:
536
524
  print_llm_error("Authentication failed", exc)
537
- raise typer.Exit(1)
525
+ raise SystemExit(1)
538
526
 
539
527
 
540
528
  def stage_changes_for_commit(
@@ -575,6 +563,8 @@ def request_commit_message(
575
563
  context: str = "",
576
564
  provider_config: providers.ProviderConfig | None = None,
577
565
  http_client_config: llm.HttpClientConfig | None = None,
566
+ disable_thinking: bool = True,
567
+ max_tokens: int = 1024,
578
568
  ) -> str:
579
569
  """Request a commit message for the provided staged state."""
580
570
  try:
@@ -587,10 +577,12 @@ def request_commit_message(
587
577
  context=context,
588
578
  provider_config=provider_config,
589
579
  http_client_config=http_client_config,
580
+ disable_thinking=disable_thinking,
581
+ max_tokens=max_tokens,
590
582
  )
591
583
  except llm.LLMError as exc:
592
584
  print_llm_error("Could not generate a commit message", exc)
593
- raise typer.Exit(1)
585
+ raise SystemExit(1)
594
586
 
595
587
 
596
588
  def request_split_commit_plan(
@@ -602,6 +594,8 @@ def request_split_commit_plan(
602
594
  context: str = "",
603
595
  provider_config: providers.ProviderConfig | None = None,
604
596
  http_client_config: llm.HttpClientConfig | None = None,
597
+ disable_thinking: bool = True,
598
+ max_tokens: int = 1024,
605
599
  ) -> SplitCommitPlan:
606
600
  """Request and validate a split-commit plan for the staged patch units."""
607
601
  planner_system_prompt = load_named_prompt(SPLIT_COMMIT_PLANNER_PROMPT_FILENAME)
@@ -622,11 +616,13 @@ def request_split_commit_plan(
622
616
  model=model,
623
617
  provider_config=provider_config,
624
618
  http_client_config=http_client_config,
619
+ disable_thinking=disable_thinking,
620
+ max_tokens=max_tokens,
625
621
  )
626
622
  except llm.LLMError as exc:
627
623
  if not should_retry_with_compact_prompt(exc):
628
624
  print_llm_error("Could not generate a split commit plan", exc)
629
- raise typer.Exit(1)
625
+ raise SystemExit(1)
630
626
 
631
627
  console.print(
632
628
  "[yellow]Staged patch units exceeded the model context window; retrying split planning with summaries only.[/yellow]"
@@ -655,10 +651,12 @@ def request_split_commit_plan(
655
651
  model=model,
656
652
  provider_config=provider_config,
657
653
  http_client_config=http_client_config,
654
+ disable_thinking=disable_thinking,
655
+ max_tokens=max_tokens,
658
656
  )
659
657
  except llm.LLMError as exc:
660
658
  print_llm_error("Could not generate a split commit plan", exc)
661
- raise typer.Exit(1)
659
+ raise SystemExit(1)
662
660
 
663
661
  return parse_split_plan_response(
664
662
  response,
@@ -674,6 +672,8 @@ def request_split_commit_messages(
674
672
  context: str = "",
675
673
  provider_config: providers.ProviderConfig | None = None,
676
674
  http_client_config: llm.HttpClientConfig | None = None,
675
+ disable_thinking: bool = True,
676
+ max_tokens: int = 1024,
677
677
  ) -> list[PreparedSplitCommit]:
678
678
  """Generate commit messages for each planned split-commit group."""
679
679
  try:
@@ -691,6 +691,8 @@ def request_split_commit_messages(
691
691
  context=context,
692
692
  provider_config=provider_config,
693
693
  http_client_config=http_client_config,
694
+ disable_thinking=disable_thinking,
695
+ max_tokens=max_tokens,
694
696
  )
695
697
 
696
698
  prepared_commits.append(
@@ -700,7 +702,7 @@ def request_split_commit_messages(
700
702
  return prepared_commits
701
703
  except llm.LLMError as exc:
702
704
  print_llm_error("Could not generate split commit messages", exc)
703
- raise typer.Exit(1)
705
+ raise SystemExit(1)
704
706
 
705
707
 
706
708
  def confirm_split_commit_count(
@@ -730,7 +732,7 @@ def confirm_split_commit_count(
730
732
  return plan
731
733
 
732
734
  console.print("Split commit plan cancelled.")
733
- raise typer.Exit()
735
+ raise SystemExit()
734
736
 
735
737
 
736
738
  def display_commit_message(commit_message: str) -> None:
@@ -769,7 +771,7 @@ def execute_commit_action(
769
771
  if yes:
770
772
  return commit_with_retry_no_verify(repo, commit_message)
771
773
 
772
- choice = typer.prompt(
774
+ choice = Prompt.ask(
773
775
  "Choose action: (c)ommit, (e)dit message, (q)uit",
774
776
  default="c",
775
777
  show_default=True,
@@ -777,7 +779,7 @@ def execute_commit_action(
777
779
 
778
780
  if choice == "q":
779
781
  console.print("Commit cancelled.")
780
- raise typer.Exit()
782
+ raise SystemExit()
781
783
  if choice == "e":
782
784
  console.print("[cyan]Opening git editor...[/cyan]")
783
785
  return commit_with_retry_no_verify(repo, commit_message, use_editor=True)
@@ -785,7 +787,7 @@ def execute_commit_action(
785
787
  return commit_with_retry_no_verify(repo, commit_message)
786
788
 
787
789
  console.print("Invalid choice. Commit cancelled.")
788
- raise typer.Exit()
790
+ raise SystemExit()
789
791
 
790
792
 
791
793
  def execute_split_commit_plan(
@@ -797,7 +799,7 @@ def execute_split_commit_plan(
797
799
  """Run the split-commit plan against temporary alternate indexes."""
798
800
  use_editor = False
799
801
  if not yes:
800
- choice = typer.prompt(
802
+ choice = Prompt.ask(
801
803
  "Choose action: (c)ommit all, (e)dit each message, (q)uit",
802
804
  default="c",
803
805
  show_default=True,
@@ -805,12 +807,12 @@ def execute_split_commit_plan(
805
807
 
806
808
  if choice == "q":
807
809
  console.print("Commit cancelled.")
808
- raise typer.Exit()
810
+ raise SystemExit()
809
811
  if choice == "e":
810
812
  use_editor = True
811
813
  elif choice != "c":
812
814
  console.print("Invalid choice. Commit cancelled.")
813
- raise typer.Exit()
815
+ raise SystemExit()
814
816
 
815
817
  execution_state = SplitCommitExecutionState(
816
818
  original_head_sha=repo.get_head_sha() if repo.has_commit("HEAD") else None,
@@ -840,16 +842,19 @@ def execute_split_commit_plan(
840
842
  console.print(
841
843
  f"[red]Failed to apply the planned changes for commit {index}: {exc}[/red]"
842
844
  )
843
- raise typer.Exit(1)
844
-
845
- commit_shas.append(
846
- commit_with_retry_no_verify(
847
- repo,
848
- prepared_commit.message,
849
- use_editor=use_editor,
850
- env=alternate_index.env,
845
+ raise SystemExit(1)
846
+
847
+ try:
848
+ commit_shas.append(
849
+ repo.create_commit_from_index(
850
+ prepared_commit.message,
851
+ index=alternate_index,
852
+ use_editor=use_editor,
853
+ )
851
854
  )
852
- )
855
+ except GitError as exc:
856
+ console.print(f"[red]Failed to create commit {index}: {exc}[/red]")
857
+ raise SystemExit(1)
853
858
  except BaseException:
854
859
  try:
855
860
  if execution_state.original_head_sha is not None:
@@ -881,6 +886,8 @@ def handle_single_commit_flow(
881
886
  context: str = "",
882
887
  provider_config: providers.ProviderConfig | None = None,
883
888
  http_client_config: llm.HttpClientConfig | None = None,
889
+ disable_thinking: bool = True,
890
+ max_tokens: int = 1024,
884
891
  ) -> None:
885
892
  """Generate, display, and execute the single-commit flow."""
886
893
  commit_message = request_commit_message(
@@ -889,6 +896,8 @@ def handle_single_commit_flow(
889
896
  context=context,
890
897
  provider_config=provider_config,
891
898
  http_client_config=http_client_config,
899
+ disable_thinking=disable_thinking,
900
+ max_tokens=max_tokens,
892
901
  )
893
902
  display_commit_message(commit_message)
894
903
 
@@ -906,6 +915,8 @@ def handle_split_commit_flow(
906
915
  context: str = "",
907
916
  provider_config: providers.ProviderConfig | None = None,
908
917
  http_client_config: llm.HttpClientConfig | None = None,
918
+ disable_thinking: bool = True,
919
+ max_tokens: int = 1024,
909
920
  ) -> None:
910
921
  """Generate, display, and execute the split-commit flow."""
911
922
  patch_units = tuple(
@@ -924,6 +935,8 @@ def handle_split_commit_flow(
924
935
  context=context,
925
936
  provider_config=provider_config,
926
937
  http_client_config=http_client_config,
938
+ disable_thinking=disable_thinking,
939
+ max_tokens=max_tokens,
927
940
  )
928
941
  return
929
942
 
@@ -939,6 +952,8 @@ def handle_split_commit_flow(
939
952
  context=context,
940
953
  provider_config=provider_config,
941
954
  http_client_config=http_client_config,
955
+ disable_thinking=disable_thinking,
956
+ max_tokens=max_tokens,
942
957
  )
943
958
  return
944
959
 
@@ -961,6 +976,8 @@ def handle_split_commit_flow(
961
976
  context=context,
962
977
  provider_config=provider_config,
963
978
  http_client_config=http_client_config,
979
+ disable_thinking=disable_thinking,
980
+ max_tokens=max_tokens,
964
981
  )
965
982
  except SplitPlanningError as exc:
966
983
  console.print(
@@ -975,6 +992,8 @@ def handle_split_commit_flow(
975
992
  context=context,
976
993
  provider_config=provider_config,
977
994
  http_client_config=http_client_config,
995
+ disable_thinking=disable_thinking,
996
+ max_tokens=max_tokens,
978
997
  )
979
998
  return
980
999
 
@@ -992,6 +1011,8 @@ def handle_split_commit_flow(
992
1011
  context=context,
993
1012
  provider_config=provider_config,
994
1013
  http_client_config=http_client_config,
1014
+ disable_thinking=disable_thinking,
1015
+ max_tokens=max_tokens,
995
1016
  )
996
1017
  prepared_commits = order_prepared_split_commits(prepared_commits)
997
1018
 
@@ -1012,22 +1033,29 @@ def handle_split_commit_flow(
1012
1033
  console.print(f"[green]{commit_sha[:8]}[/green] {prepared_commit.message}")
1013
1034
 
1014
1035
 
1015
- @app.command("authenticate")
1016
- @app.command("login", hidden=True)
1036
+ @app.command(name="authenticate")
1037
+ @app.command(name="login", show=False)
1017
1038
  def authenticate(
1018
- enterprise_domain: str | None = typer.Option(
1019
- None,
1020
- "--enterprise-domain",
1021
- help="GitHub Enterprise hostname. Omit for github.com.",
1022
- ),
1023
- force: bool = typer.Option(
1024
- False, "--force", help="Replace cached GitHub Copilot credentials"
1025
- ),
1039
+ enterprise_domain: Annotated[
1040
+ str | None,
1041
+ cyclopts.Parameter(
1042
+ name="--enterprise-domain",
1043
+ help="GitHub Enterprise hostname. Omit for github.com.",
1044
+ ),
1045
+ ] = None,
1046
+ force: Annotated[
1047
+ bool,
1048
+ cyclopts.Parameter(
1049
+ name="--force",
1050
+ help="Replace cached GitHub Copilot credentials",
1051
+ ),
1052
+ ] = False,
1026
1053
  ca_bundle: CaBundleOption = None,
1027
1054
  insecure: InsecureOption = False,
1028
- native_tls: NativeTlsOption = False,
1055
+ native_tls: NativeTlsOption = True,
1029
1056
  ):
1030
1057
  """Authenticate with GitHub Copilot and cache credentials locally."""
1058
+ print_cli_banner()
1031
1059
  http_client_config = build_http_client_config(
1032
1060
  ca_bundle=ca_bundle,
1033
1061
  insecure=insecure,
@@ -1041,19 +1069,20 @@ def authenticate(
1041
1069
  )
1042
1070
  except copilot.LLMError as exc:
1043
1071
  print_llm_error("Authentication failed", exc)
1044
- raise typer.Exit(1)
1072
+ raise SystemExit(1)
1045
1073
 
1046
1074
 
1047
- @app.command("summary")
1075
+ @app.command(name="summary")
1048
1076
  def summary(
1049
1077
  provider: ProviderOption = None,
1050
1078
  base_url: BaseUrlOption = None,
1051
1079
  api_key: ApiKeyOption = None,
1052
1080
  ca_bundle: CaBundleOption = None,
1053
1081
  insecure: InsecureOption = False,
1054
- native_tls: NativeTlsOption = False,
1082
+ native_tls: NativeTlsOption = True,
1055
1083
  ):
1056
1084
  """Show the configured LLM provider summary."""
1085
+ print_cli_banner()
1057
1086
  http_client_config = build_http_client_config(
1058
1087
  ca_bundle=ca_bundle,
1059
1088
  insecure=insecure,
@@ -1071,24 +1100,27 @@ def summary(
1071
1100
  )
1072
1101
  except llm.LLMError as exc:
1073
1102
  print_llm_error("Could not load provider summary", exc)
1074
- raise typer.Exit(1)
1103
+ raise SystemExit(1)
1075
1104
 
1076
1105
 
1077
- @app.command("models")
1106
+ @app.command(name="models")
1078
1107
  def models_command(
1079
1108
  provider: ProviderOption = None,
1080
1109
  base_url: BaseUrlOption = None,
1081
1110
  api_key: ApiKeyOption = None,
1082
- vendor: str | None = typer.Option(
1083
- None,
1084
- "--vendor",
1085
- help="Filter listed models by vendor: anthropic, gemini/google, or openai.",
1086
- ),
1111
+ vendor: Annotated[
1112
+ str | None,
1113
+ cyclopts.Parameter(
1114
+ name="--vendor",
1115
+ help="Filter listed models by vendor: anthropic, gemini/google, or openai.",
1116
+ ),
1117
+ ] = None,
1087
1118
  ca_bundle: CaBundleOption = None,
1088
1119
  insecure: InsecureOption = False,
1089
- native_tls: NativeTlsOption = False,
1120
+ native_tls: NativeTlsOption = True,
1090
1121
  ):
1091
1122
  """List available models for the configured LLM provider."""
1123
+ print_cli_banner()
1092
1124
  http_client_config = build_http_client_config(
1093
1125
  ca_bundle=ca_bundle,
1094
1126
  insecure=insecure,
@@ -1116,47 +1148,75 @@ def models_command(
1116
1148
  )
1117
1149
  except llm.LLMError as exc:
1118
1150
  print_llm_error("Could not load models", exc)
1119
- raise typer.Exit(1)
1151
+ raise SystemExit(1)
1120
1152
 
1121
1153
 
1122
- @app.command()
1154
+ @app.command
1123
1155
  def commit(
1124
- all_files: bool = typer.Option(
1125
- False, "--all", "-a", help="Stage all files before committing"
1126
- ),
1156
+ all_files: Annotated[
1157
+ bool,
1158
+ cyclopts.Parameter(
1159
+ name=("--all", "-a"),
1160
+ help="Stage all files before committing",
1161
+ ),
1162
+ ] = False,
1127
1163
  split: SplitOption = False,
1128
1164
  split_count: SplitCountOption = None,
1129
- model: str | None = typer.Option(
1130
- None,
1131
- "--model",
1132
- "-m",
1133
- metavar="MODEL_ID",
1134
- help="Model to use for generating commit message",
1135
- ),
1136
- yes: bool = typer.Option(
1137
- False, "--yes", "-y", help="Automatically accept the generated commit message"
1138
- ),
1139
- context: str = typer.Option(
1140
- "",
1141
- "--context",
1142
- "-c",
1143
- help="Optional user-provided context to guide commit message",
1144
- ),
1165
+ model: Annotated[
1166
+ str | None,
1167
+ cyclopts.Parameter(
1168
+ name=("--model", "-m"),
1169
+ help="Model to use for generating commit message",
1170
+ ),
1171
+ ] = None,
1172
+ yes: Annotated[
1173
+ bool,
1174
+ cyclopts.Parameter(
1175
+ name=("--yes", "-y"),
1176
+ help="Automatically accept the generated commit message",
1177
+ ),
1178
+ ] = False,
1179
+ context: Annotated[
1180
+ str,
1181
+ cyclopts.Parameter(
1182
+ name=("--context", "-c"),
1183
+ help="Optional user-provided context to guide commit message",
1184
+ ),
1185
+ ] = "",
1186
+ disable_thinking: Annotated[
1187
+ bool,
1188
+ cyclopts.Parameter(
1189
+ name="--disable-thinking",
1190
+ negative="--enable-thinking",
1191
+ help=(
1192
+ "Disable or minimize reasoning/thinking tokens for commit-message requests."
1193
+ ),
1194
+ ),
1195
+ ] = True,
1196
+ max_tokens: Annotated[
1197
+ int,
1198
+ cyclopts.Parameter(
1199
+ name="--max-tokens",
1200
+ help=("Maximum output tokens for LLM generation."),
1201
+ validator=cyclopts.validators.Number(gte=1),
1202
+ ),
1203
+ ] = 1024,
1145
1204
  provider: ProviderOption = None,
1146
1205
  base_url: BaseUrlOption = None,
1147
1206
  api_key: ApiKeyOption = None,
1148
1207
  ca_bundle: CaBundleOption = None,
1149
1208
  insecure: InsecureOption = False,
1150
- native_tls: NativeTlsOption = False,
1209
+ native_tls: NativeTlsOption = True,
1151
1210
  ):
1152
1211
  """
1153
1212
  Generate commit message based on changes in the current git repository and commit them.
1154
1213
  """
1214
+ print_cli_banner()
1155
1215
  try:
1156
1216
  repo = GitRepository()
1157
1217
  except NotAGitRepositoryError:
1158
1218
  console.print("[red]Error: Not in a git repository[/red]")
1159
- raise typer.Exit(1)
1219
+ raise SystemExit(1)
1160
1220
 
1161
1221
  http_client_config = build_http_client_config(
1162
1222
  ca_bundle=ca_bundle,
@@ -1171,7 +1231,7 @@ def commit(
1171
1231
  )
1172
1232
  except llm.LLMError as exc:
1173
1233
  print_llm_error("Could not resolve the LLM provider", exc)
1174
- raise typer.Exit(1)
1234
+ raise SystemExit(1)
1175
1235
 
1176
1236
  if provider_config.provider == "copilot":
1177
1237
  ensure_copilot_authentication(http_client_config)
@@ -1181,7 +1241,7 @@ def commit(
1181
1241
 
1182
1242
  if not status.files:
1183
1243
  console.print("[yellow]No changes to commit.[/yellow]")
1184
- raise typer.Exit()
1244
+ raise SystemExit()
1185
1245
 
1186
1246
  status = stage_changes_for_commit(repo, status, all_files=all_files)
1187
1247
 
@@ -1199,7 +1259,7 @@ def commit(
1199
1259
  )
1200
1260
  except llm.LLMError as exc:
1201
1261
  print_llm_error("Could not select a model", exc)
1202
- raise typer.Exit(1)
1262
+ raise SystemExit(1)
1203
1263
 
1204
1264
  display_selected_model(selected_model)
1205
1265
  model = selected_model.id
@@ -1214,6 +1274,8 @@ def commit(
1214
1274
  context=context,
1215
1275
  provider_config=provider_config,
1216
1276
  http_client_config=http_client_config,
1277
+ disable_thinking=disable_thinking,
1278
+ max_tokens=max_tokens,
1217
1279
  )
1218
1280
  return
1219
1281
 
@@ -1225,6 +1287,8 @@ def commit(
1225
1287
  context=context,
1226
1288
  provider_config=provider_config,
1227
1289
  http_client_config=http_client_config,
1290
+ disable_thinking=disable_thinking,
1291
+ max_tokens=max_tokens,
1228
1292
  )
1229
1293
 
1230
1294