git-cai-cli 0.1.0__py3-none-any.whl → 0.1.dev1__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_cai_cli/__init__.py +5 -0
- git_cai_cli/_version.py +24 -0
- git_cai_cli/cli/__init__.py +0 -0
- git_cai_cli/cli/cli.py +371 -0
- git_cai_cli/cli/helptext.py +75 -0
- git_cai_cli/cli/modes.py +145 -0
- git_cai_cli/core/completion.py +250 -0
- git_cai_cli/core/config.py +634 -66
- git_cai_cli/core/doctor.py +195 -0
- git_cai_cli/core/editors.py +23 -0
- git_cai_cli/core/gitutils.py +506 -16
- git_cai_cli/core/init.py +206 -0
- git_cai_cli/core/languages.py +120 -0
- git_cai_cli/core/llm.py +1268 -50
- git_cai_cli/core/options.py +459 -0
- git_cai_cli/core/pr.py +187 -0
- git_cai_cli/core/prompts_fallback.py +172 -0
- git_cai_cli/core/secrets.py +160 -0
- git_cai_cli/core/spinner.py +130 -0
- git_cai_cli/core/squash.py +490 -0
- git_cai_cli/core/stats.py +419 -0
- git_cai_cli/core/validate.py +350 -0
- git_cai_cli/main.py +332 -0
- git_cai_cli-0.1.dev1.data/data/share/man/man1/git-cai.1 +2021 -0
- git_cai_cli-0.1.dev1.dist-info/METADATA +523 -0
- git_cai_cli-0.1.dev1.dist-info/RECORD +31 -0
- {git_cai_cli-0.1.0.dist-info → git_cai_cli-0.1.dev1.dist-info}/WHEEL +1 -1
- git_cai_cli-0.1.dev1.dist-info/entry_points.txt +2 -0
- {git_cai_cli-0.1.0.dist-info → git_cai_cli-0.1.dev1.dist-info}/top_level.txt +0 -1
- git_cai_cli/cli.py +0 -61
- git_cai_cli-0.1.0.dist-info/METADATA +0 -104
- git_cai_cli-0.1.0.dist-info/RECORD +0 -13
- git_cai_cli-0.1.0.dist-info/entry_points.txt +0 -2
- tests/test_core/test_config.py +0 -128
- {git_cai_cli-0.1.0.dist-info → git_cai_cli-0.1.dev1.dist-info}/licenses/LICENSE +0 -0
git_cai_cli/__init__.py
CHANGED
git_cai_cli/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.dev1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev1')
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
File without changes
|
git_cai_cli/cli/cli.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI entry point for git-cai-cli.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from git_cai_cli.cli.helptext import print_help_and_exit
|
|
9
|
+
from git_cai_cli.cli.modes import resolve_mode, validate_options
|
|
10
|
+
from git_cai_cli.main import run
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(add_completion=False, help=None, no_args_is_help=False)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.callback(invoke_without_command=True)
|
|
18
|
+
def callback( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
19
|
+
version: bool = typer.Option(
|
|
20
|
+
False, "-v", "--version", help="Show version", is_eager=True
|
|
21
|
+
),
|
|
22
|
+
help_flag: bool = typer.Option(
|
|
23
|
+
False, "-h", "--help", help="Show help", is_eager=True
|
|
24
|
+
),
|
|
25
|
+
install_completion: bool = typer.Option(
|
|
26
|
+
False,
|
|
27
|
+
"--install-completion",
|
|
28
|
+
"-i",
|
|
29
|
+
help="Install shell completion for git-cai",
|
|
30
|
+
is_eager=True,
|
|
31
|
+
),
|
|
32
|
+
amend: bool = typer.Option(
|
|
33
|
+
False, "-A", "--amend", help="Regenerate and amend the last commit message"
|
|
34
|
+
),
|
|
35
|
+
branch_context: bool | None = typer.Option(
|
|
36
|
+
None,
|
|
37
|
+
"--branch/--no-branch",
|
|
38
|
+
"-b",
|
|
39
|
+
help="Include current branch name as context for the LLM. Use --no-branch to explicitly disable when the persisted config has it enabled.",
|
|
40
|
+
),
|
|
41
|
+
conventional: bool | None = typer.Option(
|
|
42
|
+
None,
|
|
43
|
+
"--conventional/--no-conventional",
|
|
44
|
+
"-C",
|
|
45
|
+
help="Use Conventional Commits format (type(scope): description). Use --no-conventional to explicitly disable when the persisted config has it enabled.",
|
|
46
|
+
),
|
|
47
|
+
crazy: bool = typer.Option(
|
|
48
|
+
False, "-c", "--crazy", help="Commit immediately without opening editor"
|
|
49
|
+
),
|
|
50
|
+
enable_debug: bool = typer.Option(
|
|
51
|
+
False, "--debug", "-d", help="Enable debug logging"
|
|
52
|
+
),
|
|
53
|
+
generate_config: bool = typer.Option(
|
|
54
|
+
False,
|
|
55
|
+
"-g",
|
|
56
|
+
"--generate-config",
|
|
57
|
+
help="Generate default cai_config.yml in the current directory",
|
|
58
|
+
),
|
|
59
|
+
generate_prompts: bool = typer.Option(
|
|
60
|
+
False,
|
|
61
|
+
"-p",
|
|
62
|
+
"--generate-prompts",
|
|
63
|
+
help="Generate default commit/squash prompt files in the current directory",
|
|
64
|
+
),
|
|
65
|
+
list_flag: bool = typer.Option(
|
|
66
|
+
False,
|
|
67
|
+
"--list",
|
|
68
|
+
"-l",
|
|
69
|
+
help="List information (e.g. languages, styles, providers, config)",
|
|
70
|
+
is_flag=True,
|
|
71
|
+
),
|
|
72
|
+
list_arg: str = typer.Argument(
|
|
73
|
+
None, help="Optional argument for --list or --squash (see docs)"
|
|
74
|
+
),
|
|
75
|
+
stage_tracked: bool = typer.Option(
|
|
76
|
+
False, "--all", "-a", help="Stage all tracked files"
|
|
77
|
+
),
|
|
78
|
+
squash: bool = typer.Option(
|
|
79
|
+
False, "--squash", "-s", help="Squash commits on this branch"
|
|
80
|
+
),
|
|
81
|
+
pr: bool = typer.Option(
|
|
82
|
+
False,
|
|
83
|
+
"--PR",
|
|
84
|
+
"-r",
|
|
85
|
+
help="Generate a Pull Request description from the commits on this branch",
|
|
86
|
+
),
|
|
87
|
+
base: str = typer.Option(
|
|
88
|
+
None,
|
|
89
|
+
"--base",
|
|
90
|
+
help="Explicit base branch for --PR (overrides auto-detection)",
|
|
91
|
+
),
|
|
92
|
+
update: bool = typer.Option(False, "--update", "-u", help="Check for updates"),
|
|
93
|
+
set_config: str = typer.Option(
|
|
94
|
+
None,
|
|
95
|
+
"-S",
|
|
96
|
+
"--set",
|
|
97
|
+
help="Set a config value in repo config (key=value). Requires existing repo config.",
|
|
98
|
+
),
|
|
99
|
+
set_home: str = typer.Option(
|
|
100
|
+
None,
|
|
101
|
+
"-H",
|
|
102
|
+
"--set-home",
|
|
103
|
+
help="Set a config value in home config (key=value). Always targets ~/.config/cai/.",
|
|
104
|
+
),
|
|
105
|
+
provider: str = typer.Option(
|
|
106
|
+
None, "--provider", "-P", help="Override LLM provider for this invocation"
|
|
107
|
+
),
|
|
108
|
+
model: str = typer.Option(
|
|
109
|
+
None, "--model", "-m", help="Override model (requires --provider)"
|
|
110
|
+
),
|
|
111
|
+
context: str = typer.Option(
|
|
112
|
+
None,
|
|
113
|
+
"-x",
|
|
114
|
+
"--context",
|
|
115
|
+
help="Extra context for the LLM (e.g. ticket number, reason for change)",
|
|
116
|
+
),
|
|
117
|
+
time_flag: bool = typer.Option(
|
|
118
|
+
False, "--time", "-t", help="Measure and log generation time"
|
|
119
|
+
),
|
|
120
|
+
timeout: int = typer.Option(
|
|
121
|
+
None,
|
|
122
|
+
"-T",
|
|
123
|
+
"--timeout",
|
|
124
|
+
help="HTTP timeout in seconds for this invocation (overrides config; default 30).",
|
|
125
|
+
),
|
|
126
|
+
full_files: bool | None = typer.Option(
|
|
127
|
+
None,
|
|
128
|
+
"--full-files/--no-full-files",
|
|
129
|
+
"-F",
|
|
130
|
+
help="Send the full contents of affected files alongside the diff. Use --no-full-files to explicitly disable when the persisted config has it enabled.",
|
|
131
|
+
),
|
|
132
|
+
files: list[str] = typer.Option(
|
|
133
|
+
None,
|
|
134
|
+
"-f",
|
|
135
|
+
"--files",
|
|
136
|
+
help="Limit the diff (and full-file content, if enabled) to these paths. Repeat for multiple files.",
|
|
137
|
+
),
|
|
138
|
+
sql: str = typer.Option(
|
|
139
|
+
None,
|
|
140
|
+
"--sql",
|
|
141
|
+
"-q",
|
|
142
|
+
help="Override stats writing for this run: --sql true / --sql false. Wins over the persisted `stats` config.",
|
|
143
|
+
),
|
|
144
|
+
stats: bool = typer.Option(
|
|
145
|
+
False,
|
|
146
|
+
"--stats",
|
|
147
|
+
"-z",
|
|
148
|
+
help="Show local-only usage analytics (commits per provider, token totals, latency).",
|
|
149
|
+
),
|
|
150
|
+
stats_since: str = typer.Option(
|
|
151
|
+
None,
|
|
152
|
+
"--since",
|
|
153
|
+
help="Filter --stats to events on or after this date (YYYY-MM-DD).",
|
|
154
|
+
),
|
|
155
|
+
stats_json: bool = typer.Option(
|
|
156
|
+
False,
|
|
157
|
+
"--json",
|
|
158
|
+
help="Render --stats output as JSON instead of text.",
|
|
159
|
+
),
|
|
160
|
+
stats_reset: bool = typer.Option(
|
|
161
|
+
False,
|
|
162
|
+
"--reset-stats",
|
|
163
|
+
help="Delete all rows from the local stats DB.",
|
|
164
|
+
),
|
|
165
|
+
init: bool = typer.Option(
|
|
166
|
+
False,
|
|
167
|
+
"--init",
|
|
168
|
+
"-I",
|
|
169
|
+
help="Interactive setup wizard: writes ~/.config/cai/cai_config.yml and tokens.yml.",
|
|
170
|
+
),
|
|
171
|
+
signoff: bool | None = typer.Option(
|
|
172
|
+
None,
|
|
173
|
+
"--signoff/--no-signoff",
|
|
174
|
+
"-o",
|
|
175
|
+
help="Append a `Signed-off-by:` trailer using git user.name/user.email. Use --no-signoff to disable when the persisted config has it enabled.",
|
|
176
|
+
),
|
|
177
|
+
print_only: bool = typer.Option(
|
|
178
|
+
False,
|
|
179
|
+
"--print",
|
|
180
|
+
help="Generate the commit message, print it to stdout, and exit without committing.",
|
|
181
|
+
),
|
|
182
|
+
temperature: float = typer.Option(
|
|
183
|
+
None,
|
|
184
|
+
"-E",
|
|
185
|
+
"--temperature",
|
|
186
|
+
help="Override the active provider's sampling temperature for this invocation.",
|
|
187
|
+
),
|
|
188
|
+
style: str = typer.Option(
|
|
189
|
+
None,
|
|
190
|
+
"-y",
|
|
191
|
+
"--style",
|
|
192
|
+
help="Override the commit message style for this invocation (e.g. funny, neutral, none).",
|
|
193
|
+
),
|
|
194
|
+
language: str = typer.Option(
|
|
195
|
+
None,
|
|
196
|
+
"-L",
|
|
197
|
+
"--language",
|
|
198
|
+
help="Override the commit message language code for this invocation (e.g. de, fr, none).",
|
|
199
|
+
),
|
|
200
|
+
emoji: bool | None = typer.Option(
|
|
201
|
+
None,
|
|
202
|
+
"-e",
|
|
203
|
+
"--emoji/--no-emoji",
|
|
204
|
+
help="Override emoji usage for this invocation. Use --no-emoji to disable when config enables it.",
|
|
205
|
+
),
|
|
206
|
+
check: bool = typer.Option(
|
|
207
|
+
False,
|
|
208
|
+
"-k",
|
|
209
|
+
"--check",
|
|
210
|
+
help="Run configuration diagnostics (offline). Add --ping for a live provider probe.",
|
|
211
|
+
),
|
|
212
|
+
ping: bool = typer.Option(
|
|
213
|
+
False,
|
|
214
|
+
"-n",
|
|
215
|
+
"--ping",
|
|
216
|
+
help="With --check, also send a tiny request to the active provider to confirm reachability.",
|
|
217
|
+
),
|
|
218
|
+
allow_secrets: bool = typer.Option(
|
|
219
|
+
False,
|
|
220
|
+
"-B",
|
|
221
|
+
"--allow-secrets",
|
|
222
|
+
help="Bypass the local secret scan and send the diff even if a likely secret is detected.",
|
|
223
|
+
),
|
|
224
|
+
):
|
|
225
|
+
"""
|
|
226
|
+
CLI entry point for git-cai-cli.
|
|
227
|
+
"""
|
|
228
|
+
if help_flag:
|
|
229
|
+
print_help_and_exit()
|
|
230
|
+
|
|
231
|
+
if version:
|
|
232
|
+
from git_cai_cli._version import __version__
|
|
233
|
+
|
|
234
|
+
typer.echo(f"cai version: {__version__}")
|
|
235
|
+
raise typer.Exit()
|
|
236
|
+
|
|
237
|
+
if install_completion:
|
|
238
|
+
from git_cai_cli.core.completion import install_completion as do_install
|
|
239
|
+
|
|
240
|
+
do_install()
|
|
241
|
+
raise typer.Exit()
|
|
242
|
+
|
|
243
|
+
mode = resolve_mode(
|
|
244
|
+
amend=amend,
|
|
245
|
+
check=check,
|
|
246
|
+
init=init,
|
|
247
|
+
list_flag=list_flag,
|
|
248
|
+
pr=pr,
|
|
249
|
+
squash=squash,
|
|
250
|
+
stats=stats,
|
|
251
|
+
update=update,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if ping and not check:
|
|
255
|
+
typer.echo("Error: --ping is only valid together with --check.", err=True)
|
|
256
|
+
raise typer.Exit(code=1)
|
|
257
|
+
|
|
258
|
+
sql_override: bool | None = None
|
|
259
|
+
if sql is not None:
|
|
260
|
+
normalized = sql.strip().lower()
|
|
261
|
+
if normalized in ("true", "1", "yes", "on"):
|
|
262
|
+
sql_override = True
|
|
263
|
+
elif normalized in ("false", "0", "no", "off"):
|
|
264
|
+
sql_override = False
|
|
265
|
+
else:
|
|
266
|
+
typer.echo(f"Error: --sql expects true/false, got {sql!r}.", err=True)
|
|
267
|
+
raise typer.Exit(code=1)
|
|
268
|
+
|
|
269
|
+
validate_options(
|
|
270
|
+
mode=mode,
|
|
271
|
+
stage_tracked=stage_tracked,
|
|
272
|
+
enable_debug=enable_debug,
|
|
273
|
+
help_flag=help_flag,
|
|
274
|
+
version_flag=version,
|
|
275
|
+
provider_override=provider,
|
|
276
|
+
model_override=model,
|
|
277
|
+
time_flag=time_flag,
|
|
278
|
+
context=context,
|
|
279
|
+
files=files,
|
|
280
|
+
print_only=print_only,
|
|
281
|
+
crazy=crazy,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if generate_config:
|
|
285
|
+
from git_cai_cli.core.options import CliManager
|
|
286
|
+
|
|
287
|
+
manager = CliManager(package_name="git-cai-cli")
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
manager.generate_config_here()
|
|
291
|
+
typer.echo("cai_config.yml created in current directory.")
|
|
292
|
+
except RuntimeError as e:
|
|
293
|
+
typer.echo(f"Error: {e}", err=True)
|
|
294
|
+
raise typer.Exit(code=1)
|
|
295
|
+
|
|
296
|
+
raise typer.Exit()
|
|
297
|
+
|
|
298
|
+
if generate_prompts:
|
|
299
|
+
from git_cai_cli.core.options import CliManager
|
|
300
|
+
|
|
301
|
+
manager = CliManager(package_name="git-cai-cli")
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
manager.generate_prompts_here()
|
|
305
|
+
typer.echo(
|
|
306
|
+
"commit_prompt.md, squash_prompt.md, full_files_prompt.md, "
|
|
307
|
+
"and pr_prompt.md created in current directory."
|
|
308
|
+
)
|
|
309
|
+
except RuntimeError as e:
|
|
310
|
+
typer.echo(f"Error: {e}", err=True)
|
|
311
|
+
raise typer.Exit(code=1)
|
|
312
|
+
|
|
313
|
+
raise typer.Exit()
|
|
314
|
+
|
|
315
|
+
if set_config or set_home:
|
|
316
|
+
from git_cai_cli.core.config import set_config_value
|
|
317
|
+
from git_cai_cli.main import configure_logging
|
|
318
|
+
|
|
319
|
+
configure_logging(enable_debug)
|
|
320
|
+
|
|
321
|
+
raw = set_home if set_home else set_config
|
|
322
|
+
force_home = set_home is not None
|
|
323
|
+
|
|
324
|
+
if "=" not in raw:
|
|
325
|
+
log.error("Invalid format: expected key=value, got '%s'", raw)
|
|
326
|
+
raise typer.Exit(code=1)
|
|
327
|
+
|
|
328
|
+
key, value = raw.split("=", 1)
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
target = set_config_value(key, value, force_home=force_home)
|
|
332
|
+
log.info("Configuration updated: %s = %s in %s", key, value, target)
|
|
333
|
+
except ValueError as e:
|
|
334
|
+
log.error("Failed to set config: %s", e)
|
|
335
|
+
raise typer.Exit(code=1)
|
|
336
|
+
|
|
337
|
+
raise typer.Exit()
|
|
338
|
+
|
|
339
|
+
run(
|
|
340
|
+
mode=mode,
|
|
341
|
+
enable_debug=enable_debug,
|
|
342
|
+
list_arg=list_arg,
|
|
343
|
+
stage_tracked=stage_tracked,
|
|
344
|
+
conventional=conventional,
|
|
345
|
+
branch_context=branch_context,
|
|
346
|
+
crazy=crazy,
|
|
347
|
+
provider_override=provider,
|
|
348
|
+
model_override=model,
|
|
349
|
+
time_flag=time_flag,
|
|
350
|
+
context=context,
|
|
351
|
+
timeout_override=timeout,
|
|
352
|
+
full_files_override=full_files,
|
|
353
|
+
files_override=files,
|
|
354
|
+
base_override=base,
|
|
355
|
+
sql_override=sql_override,
|
|
356
|
+
stats_since=stats_since,
|
|
357
|
+
stats_json=stats_json,
|
|
358
|
+
stats_reset=stats_reset,
|
|
359
|
+
signoff=signoff,
|
|
360
|
+
print_only=print_only,
|
|
361
|
+
temperature_override=temperature,
|
|
362
|
+
style_override=style,
|
|
363
|
+
language_override=language,
|
|
364
|
+
emoji_override=emoji,
|
|
365
|
+
live_check=ping,
|
|
366
|
+
allow_secrets=allow_secrets,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
if __name__ == "__main__":
|
|
371
|
+
app()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Help text for git-cai-cli.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
HOME = Path.home()
|
|
10
|
+
|
|
11
|
+
HELP_TEXT = f"""
|
|
12
|
+
Git CAI - AI-powered commit message generator
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
git cai Generate commit message from staged changes
|
|
16
|
+
|
|
17
|
+
Flags:
|
|
18
|
+
-A, --amend Regenerate and amend the last commit message
|
|
19
|
+
-a, --all Stage all modified and deleted files that are already tracked by Git
|
|
20
|
+
-B, --allow-secrets Bypass the local secret scan and send even if a secret is detected
|
|
21
|
+
-b, --branch Include current branch name as context for the LLM
|
|
22
|
+
-C, --conventional Use Conventional Commits format (type(scope): description)
|
|
23
|
+
-c, --crazy Commit immediately without opening editor (trust LLM output)
|
|
24
|
+
-d, --debug Enable debug logging
|
|
25
|
+
-E, --temperature FLOAT Override the active provider's sampling temperature for this invocation
|
|
26
|
+
-e, --emoji Toggle emoji prefixes (use --no-emoji to disable)
|
|
27
|
+
-F, --full-files Send the full contents of affected files alongside the diff
|
|
28
|
+
-f, --files PATH Limit the diff (and full files) to PATH; repeat the flag for multiple files
|
|
29
|
+
-g, --generate-config Generate default cai_config.yml in the current directory
|
|
30
|
+
-H, --set-home KEY=VALUE Set a config value in home config (~/.config/cai/)
|
|
31
|
+
-h, --help Show this help message or opens manual
|
|
32
|
+
-I, --init Interactive setup wizard (writes home config and tokens.yml)
|
|
33
|
+
-i, --install-completion Install shell completion for git-cai
|
|
34
|
+
-k, --check Run configuration diagnostics (offline; add --ping for a live probe)
|
|
35
|
+
-L, --language CODE Override the commit message language (e.g. de, fr, none)
|
|
36
|
+
-l, --list [TYPE] List information. TYPE: config, editor, language,
|
|
37
|
+
model, path, provider, style
|
|
38
|
+
-m, --model NAME Override model for this invocation (requires --provider)
|
|
39
|
+
-n, --ping With --check, probe the active provider for reachability
|
|
40
|
+
-o, --signoff Append a `Signed-off-by:` trailer (git user.name / user.email)
|
|
41
|
+
-P, --provider NAME Override LLM provider for this invocation
|
|
42
|
+
-p, --generate-prompts Generate default commit/squash/full_files/pr prompt files
|
|
43
|
+
--print Print the generated commit message to stdout and exit (no commit)
|
|
44
|
+
-q, --sql true|false Override stats writing for this run (wins over config)
|
|
45
|
+
-r, --PR Generate a Pull Request description from the commits on this branch
|
|
46
|
+
--base BRANCH Base branch for --PR (overrides auto-detection)
|
|
47
|
+
-S, --set KEY=VALUE Set a config value in repo config (requires existing repo config)
|
|
48
|
+
-s, --squash [N|HASH] Squash commits on this branch and summarize them
|
|
49
|
+
No argument: squash all commits since branch checkout
|
|
50
|
+
Number: squash the last N commits
|
|
51
|
+
Hash: squash up to and including that commit
|
|
52
|
+
-T, --timeout SECONDS HTTP timeout in seconds (overrides config; default 30)
|
|
53
|
+
-t, --time Measure and log commit message generation time
|
|
54
|
+
-u, --update Check for updates
|
|
55
|
+
-v, --version Show installed version
|
|
56
|
+
-x, --context TEXT Provide extra context for the LLM (e.g. ticket number, reason)
|
|
57
|
+
-y, --style NAME Override the commit message style (e.g. funny, neutral, none)
|
|
58
|
+
-z, --stats Show local-only usage analytics (commits, tokens, latency)
|
|
59
|
+
--since YYYY-MM-DD Filter --stats to events on or after this date
|
|
60
|
+
--json Render --stats output as JSON instead of text
|
|
61
|
+
--reset-stats Delete all rows from the local stats DB
|
|
62
|
+
|
|
63
|
+
Configuration:
|
|
64
|
+
Tokens are loaded from {HOME}/.config/cai/tokens.yml
|
|
65
|
+
Reset to default config by deleting {HOME}/.config/cai/cai_config.yml
|
|
66
|
+
and executing 'git cai' again.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def print_help_and_exit() -> None:
|
|
71
|
+
"""
|
|
72
|
+
Print help text and exit.
|
|
73
|
+
"""
|
|
74
|
+
typer.echo(HELP_TEXT)
|
|
75
|
+
raise typer.Exit()
|
git_cai_cli/cli/modes.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for handling operational modes and validating command-line options.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum, auto
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Mode(Enum):
|
|
11
|
+
"""
|
|
12
|
+
Enum representing the different operational modes of the CLI.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
AMEND = auto()
|
|
16
|
+
CHECK = auto()
|
|
17
|
+
COMMIT = auto()
|
|
18
|
+
INIT = auto()
|
|
19
|
+
LIST = auto()
|
|
20
|
+
PR = auto()
|
|
21
|
+
SQUASH = auto()
|
|
22
|
+
STATS = auto()
|
|
23
|
+
UPDATE = auto()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def resolve_mode(
|
|
27
|
+
*,
|
|
28
|
+
amend: bool,
|
|
29
|
+
check: bool = False,
|
|
30
|
+
init: bool = False,
|
|
31
|
+
list_flag: bool,
|
|
32
|
+
pr: bool,
|
|
33
|
+
squash: bool,
|
|
34
|
+
stats: bool = False,
|
|
35
|
+
update: bool,
|
|
36
|
+
) -> Mode:
|
|
37
|
+
"""
|
|
38
|
+
Resolves the operational mode based on the provided flags.
|
|
39
|
+
"""
|
|
40
|
+
flags = [amend, check, init, list_flag, pr, squash, stats, update]
|
|
41
|
+
if sum(flags) > 1:
|
|
42
|
+
typer.echo(
|
|
43
|
+
"Error: --amend, --check, --init, --list, --PR, --squash, --stats, and "
|
|
44
|
+
"--update cannot be used together.",
|
|
45
|
+
err=True,
|
|
46
|
+
)
|
|
47
|
+
raise typer.Exit(code=1)
|
|
48
|
+
|
|
49
|
+
if amend:
|
|
50
|
+
return Mode.AMEND
|
|
51
|
+
if check:
|
|
52
|
+
return Mode.CHECK
|
|
53
|
+
if init:
|
|
54
|
+
return Mode.INIT
|
|
55
|
+
if list_flag:
|
|
56
|
+
return Mode.LIST
|
|
57
|
+
if pr:
|
|
58
|
+
return Mode.PR
|
|
59
|
+
if squash:
|
|
60
|
+
return Mode.SQUASH
|
|
61
|
+
if stats:
|
|
62
|
+
return Mode.STATS
|
|
63
|
+
if update:
|
|
64
|
+
return Mode.UPDATE
|
|
65
|
+
|
|
66
|
+
return Mode.COMMIT
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_options(
|
|
70
|
+
*,
|
|
71
|
+
mode: Mode,
|
|
72
|
+
stage_tracked: bool,
|
|
73
|
+
enable_debug: bool,
|
|
74
|
+
help_flag: bool,
|
|
75
|
+
version_flag: bool,
|
|
76
|
+
provider_override: str | None = None,
|
|
77
|
+
model_override: str | None = None,
|
|
78
|
+
time_flag: bool = False,
|
|
79
|
+
context: str | None = None,
|
|
80
|
+
files: list[str] | None = None,
|
|
81
|
+
print_only: bool = False,
|
|
82
|
+
crazy: bool = False,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Validates the combination of command-line options provided by the user.
|
|
86
|
+
"""
|
|
87
|
+
if enable_debug and (help_flag or version_flag):
|
|
88
|
+
typer.echo(
|
|
89
|
+
"Error: --debug cannot be used with --help or --version.",
|
|
90
|
+
err=True,
|
|
91
|
+
)
|
|
92
|
+
raise typer.Exit(code=1)
|
|
93
|
+
|
|
94
|
+
if stage_tracked and mode not in (Mode.COMMIT, Mode.AMEND):
|
|
95
|
+
typer.echo(
|
|
96
|
+
"Error: --all cannot be used with --init, --list, --update, --PR, or --squash.",
|
|
97
|
+
err=True,
|
|
98
|
+
)
|
|
99
|
+
raise typer.Exit(code=1)
|
|
100
|
+
|
|
101
|
+
if (provider_override or model_override) and mode in (
|
|
102
|
+
Mode.INIT,
|
|
103
|
+
Mode.LIST,
|
|
104
|
+
Mode.UPDATE,
|
|
105
|
+
):
|
|
106
|
+
typer.echo(
|
|
107
|
+
"Error: --provider/--model cannot be used with --init, --list, or --update.",
|
|
108
|
+
err=True,
|
|
109
|
+
)
|
|
110
|
+
raise typer.Exit(code=1)
|
|
111
|
+
|
|
112
|
+
if time_flag and mode in (Mode.INIT, Mode.LIST, Mode.UPDATE):
|
|
113
|
+
typer.echo(
|
|
114
|
+
"Error: --time cannot be used with --init, --list, or --update.",
|
|
115
|
+
err=True,
|
|
116
|
+
)
|
|
117
|
+
raise typer.Exit(code=1)
|
|
118
|
+
|
|
119
|
+
if context and mode not in (Mode.COMMIT, Mode.AMEND, Mode.SQUASH, Mode.PR):
|
|
120
|
+
typer.echo(
|
|
121
|
+
"Error: --context cannot be used with --init, --list, or --update.",
|
|
122
|
+
err=True,
|
|
123
|
+
)
|
|
124
|
+
raise typer.Exit(code=1)
|
|
125
|
+
|
|
126
|
+
if files and mode not in (Mode.COMMIT, Mode.AMEND):
|
|
127
|
+
typer.echo(
|
|
128
|
+
"Error: --files cannot be used with --init, --list, --update, --PR, or --squash.",
|
|
129
|
+
err=True,
|
|
130
|
+
)
|
|
131
|
+
raise typer.Exit(code=1)
|
|
132
|
+
|
|
133
|
+
if print_only:
|
|
134
|
+
if mode not in (Mode.COMMIT, Mode.AMEND):
|
|
135
|
+
typer.echo(
|
|
136
|
+
"Error: --print can only be used in COMMIT or AMEND mode.",
|
|
137
|
+
err=True,
|
|
138
|
+
)
|
|
139
|
+
raise typer.Exit(code=1)
|
|
140
|
+
if crazy:
|
|
141
|
+
typer.echo(
|
|
142
|
+
"Error: --print and --crazy are mutually exclusive.",
|
|
143
|
+
err=True,
|
|
144
|
+
)
|
|
145
|
+
raise typer.Exit(code=1)
|