git-cai-cli 0.1.1.dev0__tar.gz → 0.2.0__tar.gz
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-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.pylintrc +2 -1
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/Makefile +6 -2
- git_cai_cli-0.2.0/PKG-INFO +142 -0
- git_cai_cli-0.2.0/README.md +113 -0
- git_cai_cli-0.2.0/archlinux/PKGBUILD +23 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/pyproject.toml +7 -4
- git_cai_cli-0.2.0/src/git_cai_cli/cli.py +111 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/core/config.py +44 -2
- git_cai_cli-0.2.0/src/git_cai_cli/core/languages.py +100 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/core/llm.py +21 -6
- git_cai_cli-0.2.0/src/git_cai_cli/core/options.py +178 -0
- git_cai_cli-0.2.0/src/git_cai_cli.egg-info/PKG-INFO +142 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli.egg-info/SOURCES.txt +3 -1
- git_cai_cli-0.2.0/src/git_cai_cli.egg-info/entry_points.txt +2 -0
- git_cai_cli-0.2.0/src/git_cai_cli.egg-info/requires.txt +5 -0
- git_cai_cli-0.2.0/src/tests/test_core/test_config.py +267 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/uv.lock +544 -124
- git_cai_cli-0.1.1.dev0/PKG-INFO +0 -104
- git_cai_cli-0.1.1.dev0/PKGBUILD +0 -36
- git_cai_cli-0.1.1.dev0/README.md +0 -77
- git_cai_cli-0.1.1.dev0/src/git_cai_cli/cli.py +0 -61
- git_cai_cli-0.1.1.dev0/src/git_cai_cli.egg-info/PKG-INFO +0 -104
- git_cai_cli-0.1.1.dev0/src/git_cai_cli.egg-info/entry_points.txt +0 -2
- git_cai_cli-0.1.1.dev0/src/git_cai_cli.egg-info/requires.txt +0 -3
- git_cai_cli-0.1.1.dev0/src/tests/test_core/test_config.py +0 -128
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.caiignore +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.gitignore +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.bandit.yml +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.checkov.yml +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.flake8 +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.ls-lint.yml +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.markdown-link-check.json +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.markdownlint.json +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.proselintrc +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.yamllint.yml +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/check_git_branch_name.sh +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/lychee.toml +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/pyrightconfig.json +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.mega-linter.yml +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.semgrepignore +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.trivyignore +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/LICENSE +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/setup.cfg +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/__init__.py +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/core/__init__.py +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/core/gitutils.py +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
- {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli.egg-info/top_level.txt +0 -0
|
@@ -91,7 +91,8 @@ logging-modules=logging # Logging library to check against
|
|
|
91
91
|
confidence=HIGH,CONTROL_FLOW,INFERENCE,INFERENCE_FAILURE,UNDEFINED
|
|
92
92
|
# Only show warnings with these confidence levels
|
|
93
93
|
disable=logging-too-many-args,
|
|
94
|
-
import-error
|
|
94
|
+
import-error,
|
|
95
|
+
wrong-import-order
|
|
95
96
|
# Disable noisy/meta warnings
|
|
96
97
|
|
|
97
98
|
|
|
@@ -4,7 +4,7 @@ PIP := pipx
|
|
|
4
4
|
UV := uv
|
|
5
5
|
SHELL := /bin/bash
|
|
6
6
|
|
|
7
|
-
.PHONY: help lint check-docker check-npx lint-fix add-lint-hook clean
|
|
7
|
+
.PHONY: help lint check-docker check-npx lint-fix add-lint-hook clean test
|
|
8
8
|
|
|
9
9
|
help: ## Shows this help message
|
|
10
10
|
@echo "Available commands:"
|
|
@@ -14,7 +14,9 @@ add-lint-hook: ## Adds a git pre-push hook to automatically run 'lint' before pu
|
|
|
14
14
|
@echo "#!/bin/bash" > .git/hooks/pre-push
|
|
15
15
|
@echo "make lint" >> .git/hooks/pre-push
|
|
16
16
|
@chmod +x .git/hooks/pre-push
|
|
17
|
+
@echo "make test" >> .git/hooks/pre-push
|
|
17
18
|
@echo "Pre-push hook added. The 'lint' command will now run before each push."
|
|
19
|
+
@echo "Pre-push hook added. The 'test' command will now run before each push."
|
|
18
20
|
|
|
19
21
|
check-docker: ## Checks if docker is installed
|
|
20
22
|
@if ! command -v docker &> /dev/null; then \
|
|
@@ -38,10 +40,12 @@ clean: ## Clean cache of uv and delete virtual environment
|
|
|
38
40
|
@$(UV) cache clean
|
|
39
41
|
@rm -rf .venv
|
|
40
42
|
|
|
41
|
-
|
|
42
43
|
lint:
|
|
43
44
|
@sh ./.linters/check_git_branch_name.sh
|
|
44
45
|
@npx mega-linter-runner --flavor python
|
|
45
46
|
|
|
46
47
|
lint-fix: ## Lints the code using sqlfluff and fixes the issues
|
|
47
48
|
@npx mega-linter-runner --fix
|
|
49
|
+
|
|
50
|
+
test: ## Runs tests
|
|
51
|
+
@$(UV) run pytest
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: git-cai-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Use LLM to create git commit messages
|
|
5
|
+
Author-email: Thorsten Foltz <thorsten.foltz@live.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/thorstenfoltz/cai
|
|
8
|
+
Project-URL: Issues, https://github.com/thorstenfoltz/cai/issues
|
|
9
|
+
Keywords: Git,LLM,Commit,AI,GenAI
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: openai>=1.0
|
|
25
|
+
Requires-Dist: google-genai>=1.39
|
|
26
|
+
Requires-Dist: typer>=0.19.2
|
|
27
|
+
Requires-Dist: requests>=2.32.5
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# cai
|
|
31
|
+
|
|
32
|
+
cai is a Git extension written in Python that automates the creation of commit messages. With it, you can simply run `git cai` to automatically generate a commit message based on the changes and new files in your repository.
|
|
33
|
+
|
|
34
|
+
cai uses a large language model (LLM) to produce commit messages that are meaningful and context-aware. It currently supports both the OpenAI API and the Gemini API for message generation.
|
|
35
|
+
|
|
36
|
+
## Table of Contents
|
|
37
|
+
|
|
38
|
+
- [About](#about-section)
|
|
39
|
+
- [Prerequisites](#prerequisites)
|
|
40
|
+
- [Features](#features-section)
|
|
41
|
+
- [Installation](#installation-section)
|
|
42
|
+
- [Usage](#usage-section)
|
|
43
|
+
- [Configuration](#config-section)
|
|
44
|
+
- [CLI](#cli)
|
|
45
|
+
- [License](#license-section)
|
|
46
|
+
|
|
47
|
+
<h2 id="about-section">About</h2>
|
|
48
|
+
|
|
49
|
+
cai is designed to simplify your Git workflow by automatically generating commit messages using an LLM. No more struggling to summarize changes, just run git cai, and it handles it for you.
|
|
50
|
+
|
|
51
|
+
Currently, the only supported backends are the OpenAI and Gemini APIs, but additional LLM integrations may be added in the future.
|
|
52
|
+
|
|
53
|
+
<h2 id="prerequisites">Prerequisites</h2>
|
|
54
|
+
|
|
55
|
+
- Python 3.10 or higher
|
|
56
|
+
- [Pipx](https://pypi.org/project/pipx/) or [Pip](https://pypi.org/project/pip/) if installed in a virtual environment
|
|
57
|
+
- API key, currently supported
|
|
58
|
+
- OpenAI
|
|
59
|
+
- Gemini
|
|
60
|
+
|
|
61
|
+
<h2 id="features-section">Features</h2>
|
|
62
|
+
|
|
63
|
+
- Automatically detects added, modified, and deleted files
|
|
64
|
+
- Generates meaningful, context-aware commit messages using an LLM
|
|
65
|
+
- Seamless integration with Git as a plugin/extension
|
|
66
|
+
- Supports different LLM models and languages for each repository, as well as global configuration
|
|
67
|
+
|
|
68
|
+
<h2 id="installation-section">Installation</h2>
|
|
69
|
+
|
|
70
|
+
You can install cai using pipx:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
pipx install git-cai-cli
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
After installation, make sure cai is added to your `PATH`:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
pipx ensurepath
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then, restart your shell (e.g., bash, zsh, or whichever shell you use) for the changes to take effect.
|
|
83
|
+
|
|
84
|
+
<h2 id="usage-section">Usage</h2>
|
|
85
|
+
|
|
86
|
+
Once installed, cai works like a standard Git command:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
git cai
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`cai` uses Git’s `diff` output as input for generating commit messages.
|
|
93
|
+
To exclude specific files or directories from being included in the generated commit message, create a `.caiignore` file in the root of your repository. This file works like a `.gitignore`.
|
|
94
|
+
|
|
95
|
+
- Files listed in `.gitignore` are **always excluded**.
|
|
96
|
+
- `.caiignore` is only needed for files that are tracked by Git but should **not** be included in the commit message.
|
|
97
|
+
|
|
98
|
+
<h2 id="config-section">Configuration</h2>
|
|
99
|
+
|
|
100
|
+
The first time you run `git cai`, it automatically creates two configuration files:
|
|
101
|
+
|
|
102
|
+
- `cai_config.yml` – Stores general settings:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
home/<USERNAME>/.config/cai/cai_config.yml
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `tokens.yml` – Stores your API token(s):
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
home/<USERNAME>/.config/cai/tokens.yml
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Add your OpenAI and/or Gemini API token to `tokens.yml` so that cai can use it each time you generate a commit message.
|
|
115
|
+
|
|
116
|
+
If a `cai_config.yml` file exists in the root of your repository, cai will use the settings defined there. Otherwise, it falls back to the default settings.
|
|
117
|
+
|
|
118
|
+
To use a repository-specific configuration, copy the config file to the root of your repository and adjust it as needed:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
cp ~/.config/cai/cai_config.yml .
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Currently, the following options can be customized:
|
|
125
|
+
|
|
126
|
+
- default: set the default provider
|
|
127
|
+
- model: specify which model of the provider to use
|
|
128
|
+
- temperature: control how creative the model’s responses are
|
|
129
|
+
- language: set the language in which the LLM should generate commit messages
|
|
130
|
+
|
|
131
|
+
<h2 id="cli">CLI</h2>
|
|
132
|
+
|
|
133
|
+
Besides running `git cai` to generate commit messages, you can use the following options:
|
|
134
|
+
|
|
135
|
+
- `-h` shows a brief help message with available commands
|
|
136
|
+
- `d`, `--debug` enables debug logging to help troubleshoot issues
|
|
137
|
+
- `l`, `--languages` list available languages
|
|
138
|
+
- `u`, `--update` checks for updates the `cai` tool
|
|
139
|
+
- `v`, `--version` displays the currently installed version
|
|
140
|
+
|
|
141
|
+
<h2 id="license-section">License</h2>
|
|
142
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# cai
|
|
2
|
+
|
|
3
|
+
cai is a Git extension written in Python that automates the creation of commit messages. With it, you can simply run `git cai` to automatically generate a commit message based on the changes and new files in your repository.
|
|
4
|
+
|
|
5
|
+
cai uses a large language model (LLM) to produce commit messages that are meaningful and context-aware. It currently supports both the OpenAI API and the Gemini API for message generation.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [About](#about-section)
|
|
10
|
+
- [Prerequisites](#prerequisites)
|
|
11
|
+
- [Features](#features-section)
|
|
12
|
+
- [Installation](#installation-section)
|
|
13
|
+
- [Usage](#usage-section)
|
|
14
|
+
- [Configuration](#config-section)
|
|
15
|
+
- [CLI](#cli)
|
|
16
|
+
- [License](#license-section)
|
|
17
|
+
|
|
18
|
+
<h2 id="about-section">About</h2>
|
|
19
|
+
|
|
20
|
+
cai is designed to simplify your Git workflow by automatically generating commit messages using an LLM. No more struggling to summarize changes, just run git cai, and it handles it for you.
|
|
21
|
+
|
|
22
|
+
Currently, the only supported backends are the OpenAI and Gemini APIs, but additional LLM integrations may be added in the future.
|
|
23
|
+
|
|
24
|
+
<h2 id="prerequisites">Prerequisites</h2>
|
|
25
|
+
|
|
26
|
+
- Python 3.10 or higher
|
|
27
|
+
- [Pipx](https://pypi.org/project/pipx/) or [Pip](https://pypi.org/project/pip/) if installed in a virtual environment
|
|
28
|
+
- API key, currently supported
|
|
29
|
+
- OpenAI
|
|
30
|
+
- Gemini
|
|
31
|
+
|
|
32
|
+
<h2 id="features-section">Features</h2>
|
|
33
|
+
|
|
34
|
+
- Automatically detects added, modified, and deleted files
|
|
35
|
+
- Generates meaningful, context-aware commit messages using an LLM
|
|
36
|
+
- Seamless integration with Git as a plugin/extension
|
|
37
|
+
- Supports different LLM models and languages for each repository, as well as global configuration
|
|
38
|
+
|
|
39
|
+
<h2 id="installation-section">Installation</h2>
|
|
40
|
+
|
|
41
|
+
You can install cai using pipx:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
pipx install git-cai-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
After installation, make sure cai is added to your `PATH`:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pipx ensurepath
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then, restart your shell (e.g., bash, zsh, or whichever shell you use) for the changes to take effect.
|
|
54
|
+
|
|
55
|
+
<h2 id="usage-section">Usage</h2>
|
|
56
|
+
|
|
57
|
+
Once installed, cai works like a standard Git command:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
git cai
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`cai` uses Git’s `diff` output as input for generating commit messages.
|
|
64
|
+
To exclude specific files or directories from being included in the generated commit message, create a `.caiignore` file in the root of your repository. This file works like a `.gitignore`.
|
|
65
|
+
|
|
66
|
+
- Files listed in `.gitignore` are **always excluded**.
|
|
67
|
+
- `.caiignore` is only needed for files that are tracked by Git but should **not** be included in the commit message.
|
|
68
|
+
|
|
69
|
+
<h2 id="config-section">Configuration</h2>
|
|
70
|
+
|
|
71
|
+
The first time you run `git cai`, it automatically creates two configuration files:
|
|
72
|
+
|
|
73
|
+
- `cai_config.yml` – Stores general settings:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
home/<USERNAME>/.config/cai/cai_config.yml
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- `tokens.yml` – Stores your API token(s):
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
home/<USERNAME>/.config/cai/tokens.yml
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Add your OpenAI and/or Gemini API token to `tokens.yml` so that cai can use it each time you generate a commit message.
|
|
86
|
+
|
|
87
|
+
If a `cai_config.yml` file exists in the root of your repository, cai will use the settings defined there. Otherwise, it falls back to the default settings.
|
|
88
|
+
|
|
89
|
+
To use a repository-specific configuration, copy the config file to the root of your repository and adjust it as needed:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
cp ~/.config/cai/cai_config.yml .
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Currently, the following options can be customized:
|
|
96
|
+
|
|
97
|
+
- default: set the default provider
|
|
98
|
+
- model: specify which model of the provider to use
|
|
99
|
+
- temperature: control how creative the model’s responses are
|
|
100
|
+
- language: set the language in which the LLM should generate commit messages
|
|
101
|
+
|
|
102
|
+
<h2 id="cli">CLI</h2>
|
|
103
|
+
|
|
104
|
+
Besides running `git cai` to generate commit messages, you can use the following options:
|
|
105
|
+
|
|
106
|
+
- `-h` shows a brief help message with available commands
|
|
107
|
+
- `d`, `--debug` enables debug logging to help troubleshoot issues
|
|
108
|
+
- `l`, `--languages` list available languages
|
|
109
|
+
- `u`, `--update` checks for updates the `cai` tool
|
|
110
|
+
- `v`, `--version` displays the currently installed version
|
|
111
|
+
|
|
112
|
+
<h2 id="license-section">License</h2>
|
|
113
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Maintainer: Thorsten Foltz <thorsten.foltz@live.com>
|
|
2
|
+
pkgname=git-cai-cli
|
|
3
|
+
pkgver=0.1.1
|
|
4
|
+
pkgrel=1
|
|
5
|
+
pkgdesc="Use LLM to create git commit messages."
|
|
6
|
+
arch=('any')
|
|
7
|
+
url="https://github.com/thorstenfoltz/cai"
|
|
8
|
+
license=('MIT')
|
|
9
|
+
depends=('python' 'python-yaml' 'python-openai' 'python-google-genai')
|
|
10
|
+
makedepends=(python-build python-installer python-setuptools python-wheel)
|
|
11
|
+
source=("https://files.pythonhosted.org/packages/e6/c3/99bb2d87c16628017468c9ae6dd18ee2e63644109150bee0450a9f22b83f/git_cai_cli-0.1.1.tar.gz")
|
|
12
|
+
sha256sums=('36fb55223ad430b7d3dc6bf60fd4e04f6ac567acee5546d5aa8c2df781061461')
|
|
13
|
+
|
|
14
|
+
build() {
|
|
15
|
+
cd "${srcdir}/git_cai_cli-${pkgver}"
|
|
16
|
+
python -m build --wheel --no-isolation
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
package() {
|
|
20
|
+
cd "${srcdir}/git_cai_cli-${pkgver}"
|
|
21
|
+
python -m installer --destdir="$pkgdir" dist/*.whl
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "git-cai-cli"
|
|
3
3
|
dynamic = ["version"]
|
|
4
|
-
description = "Use LLM to create git
|
|
4
|
+
description = "Use LLM to create git commit messages"
|
|
5
5
|
authors = [{ name = "Thorsten Foltz", email = "thorsten.foltz@live.com" }]
|
|
6
6
|
keywords = ["Git", "LLM", "Commit", "AI", "GenAI"]
|
|
7
7
|
readme = "README.md"
|
|
@@ -9,11 +9,13 @@ requires-python = ">=3.10"
|
|
|
9
9
|
dependencies = [
|
|
10
10
|
"pyyaml>=6.0",
|
|
11
11
|
"openai>=1.0",
|
|
12
|
-
"google-genai>=1.
|
|
12
|
+
"google-genai>=1.39",
|
|
13
|
+
"typer>=0.19.2",
|
|
14
|
+
"requests>=2.32.5",
|
|
13
15
|
]
|
|
14
16
|
license = { text = "MIT" }
|
|
15
17
|
classifiers = [
|
|
16
|
-
"Development Status ::
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
17
19
|
"Environment :: Console",
|
|
18
20
|
"Intended Audience :: Developers",
|
|
19
21
|
"Programming Language :: Python :: 3",
|
|
@@ -30,7 +32,7 @@ Homepage = "https://github.com/thorstenfoltz/cai"
|
|
|
30
32
|
Issues = "https://github.com/thorstenfoltz/cai/issues"
|
|
31
33
|
|
|
32
34
|
[project.scripts]
|
|
33
|
-
git-cai = "git_cai_cli.cli:
|
|
35
|
+
git-cai = "git_cai_cli.cli:app"
|
|
34
36
|
|
|
35
37
|
[build-system]
|
|
36
38
|
requires = ["setuptools>=64", "setuptools-scm>=8"]
|
|
@@ -47,4 +49,5 @@ local_scheme = "no-local-version"
|
|
|
47
49
|
dev = [
|
|
48
50
|
"build>=1.3.0",
|
|
49
51
|
"pytest>=8.4.2",
|
|
52
|
+
"twine>=6.2.0",
|
|
50
53
|
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main function
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from git_cai_cli.core.config import get_default_config, load_config, load_token
|
|
12
|
+
from git_cai_cli.core.gitutils import find_git_root, git_diff_excluding
|
|
13
|
+
from git_cai_cli.core.llm import CommitMessageGenerator
|
|
14
|
+
from git_cai_cli.core.options import CliManager
|
|
15
|
+
|
|
16
|
+
logging.basicConfig(
|
|
17
|
+
level=logging.INFO,
|
|
18
|
+
format="%(asctime)s.%(msecs)03d [%(levelname)s] %(message)s",
|
|
19
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
20
|
+
)
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
app = typer.Typer(add_completion=True, help=None, no_args_is_help=False)
|
|
24
|
+
|
|
25
|
+
manager = CliManager(package_name="git-cai-cli")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
"""
|
|
30
|
+
Check for git repo, load access tokens and run git cai
|
|
31
|
+
"""
|
|
32
|
+
# Ensure invoked as 'git cai'
|
|
33
|
+
# Only enforce this if we're not just asking for version/help
|
|
34
|
+
if not any(flag in sys.argv for flag in ("--version", "-v", "--help", "-h")):
|
|
35
|
+
invoked_as = Path(sys.argv[0]).name
|
|
36
|
+
if not invoked_as.startswith("git-"):
|
|
37
|
+
print("This command must be run as 'git cai'", file=sys.stderr)
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
|
|
40
|
+
# Find the git repo root
|
|
41
|
+
repo_root = find_git_root()
|
|
42
|
+
if not repo_root:
|
|
43
|
+
log.error("Not inside a Git repository.")
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
# Load configuration and token
|
|
47
|
+
config = load_config()
|
|
48
|
+
default_model = get_default_config()
|
|
49
|
+
log.debug("Default model from config: %s", default_model)
|
|
50
|
+
token = load_token(default_model)
|
|
51
|
+
if not token:
|
|
52
|
+
log.error("Missing %s token in ~/.config/cai/tokens.yml", default_model)
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
|
|
55
|
+
# Get git diff
|
|
56
|
+
diff = git_diff_excluding(repo_root)
|
|
57
|
+
if not diff.strip():
|
|
58
|
+
log.info("No changes to commit. Did you run 'git add'? Files must be staged.")
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
|
|
61
|
+
# Generate commit message
|
|
62
|
+
generator = CommitMessageGenerator(token, config, default_model)
|
|
63
|
+
commit_message = generator.generate(diff)
|
|
64
|
+
|
|
65
|
+
# Open git commit editor with the generated message
|
|
66
|
+
subprocess.run(["git", "commit", "--edit", "-m", commit_message], check=True)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command()
|
|
70
|
+
def run(
|
|
71
|
+
help_flag: bool = typer.Option(False, "-h", help="Show help", is_eager=True),
|
|
72
|
+
enable_debug: bool = typer.Option(
|
|
73
|
+
False, "--debug", "-d", help="Enable debug logging", is_eager=True
|
|
74
|
+
),
|
|
75
|
+
language: bool = typer.Option(
|
|
76
|
+
False, "--languages", "-l", help="List supported languages", is_eager=True
|
|
77
|
+
),
|
|
78
|
+
update: bool = typer.Option(
|
|
79
|
+
False, "--update", "-u", help="Check for updates", is_eager=True
|
|
80
|
+
),
|
|
81
|
+
version: bool = typer.Option(
|
|
82
|
+
False, "--version", "-v", help="Show version", is_eager=True
|
|
83
|
+
),
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
Main entry point for the CLI
|
|
87
|
+
"""
|
|
88
|
+
if help_flag:
|
|
89
|
+
typer.echo(manager.get_help())
|
|
90
|
+
raise typer.Exit()
|
|
91
|
+
|
|
92
|
+
if enable_debug:
|
|
93
|
+
typer.echo(manager.enable_debug())
|
|
94
|
+
|
|
95
|
+
if language:
|
|
96
|
+
typer.echo(manager.print_available_languages())
|
|
97
|
+
raise typer.Exit()
|
|
98
|
+
|
|
99
|
+
if update:
|
|
100
|
+
typer.echo(manager.check_and_update())
|
|
101
|
+
raise typer.Exit()
|
|
102
|
+
|
|
103
|
+
if version:
|
|
104
|
+
typer.echo(manager.get_version())
|
|
105
|
+
raise typer.Exit()
|
|
106
|
+
|
|
107
|
+
main()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
app()
|
|
@@ -10,6 +10,7 @@ from typing import Any, Optional
|
|
|
10
10
|
|
|
11
11
|
import yaml
|
|
12
12
|
from git_cai_cli.core.gitutils import find_git_root
|
|
13
|
+
from git_cai_cli.core.languages import ALLOWED_LANGUAGES
|
|
13
14
|
|
|
14
15
|
log = logging.getLogger(__name__)
|
|
15
16
|
|
|
@@ -20,6 +21,7 @@ TOKENS_FILE = CONFIG_DIR / "tokens.yml"
|
|
|
20
21
|
DEFAULT_CONFIG = {
|
|
21
22
|
"openai": {"model": "gpt-4.1", "temperature": 0},
|
|
22
23
|
"gemini": {"model": "gemini-2.5-flash", "temperature": 0},
|
|
24
|
+
"language": "en",
|
|
23
25
|
"default": "openai",
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -32,14 +34,20 @@ TOKEN_TEMPLATE = {
|
|
|
32
34
|
def load_config(
|
|
33
35
|
fallback_config_file: Path = FALLBACK_CONFIG_FILE,
|
|
34
36
|
default_config: Optional[dict[str, Any]] = None,
|
|
37
|
+
allowed_languages: Optional[set[str]] = None,
|
|
35
38
|
) -> dict[str, Any]:
|
|
36
39
|
"""
|
|
37
|
-
Load configuration of LLM
|
|
40
|
+
Load configuration of LLM and validate structure and language.
|
|
38
41
|
"""
|
|
39
42
|
if default_config is None:
|
|
40
43
|
default_config = DEFAULT_CONFIG.copy()
|
|
41
44
|
log.debug("Loading config...")
|
|
42
45
|
|
|
46
|
+
languages: set[str] = (
|
|
47
|
+
ALLOWED_LANGUAGES.copy() if allowed_languages is None else allowed_languages
|
|
48
|
+
)
|
|
49
|
+
log.debug("Loading allowed languages...")
|
|
50
|
+
|
|
43
51
|
repo_root = find_git_root()
|
|
44
52
|
repo_config_file = Path(repo_root) / "cai_config.yml" if repo_root else None
|
|
45
53
|
|
|
@@ -48,6 +56,8 @@ def load_config(
|
|
|
48
56
|
with open(repo_config_file, "r", encoding="utf-8") as f:
|
|
49
57
|
config = yaml.safe_load(f) or {}
|
|
50
58
|
if config:
|
|
59
|
+
_validate_config_keys(config, DEFAULT_CONFIG)
|
|
60
|
+
config["language"] = _validate_language(config, languages)
|
|
51
61
|
return config
|
|
52
62
|
except yaml.YAMLError as e:
|
|
53
63
|
log.error("Failed to parse repo config: %s", e)
|
|
@@ -60,13 +70,18 @@ def load_config(
|
|
|
60
70
|
fallback_config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
61
71
|
with open(fallback_config_file, "w", encoding="utf-8") as f:
|
|
62
72
|
yaml.safe_dump(default_config, f)
|
|
73
|
+
default_config["language"] = _validate_language(default_config, languages)
|
|
63
74
|
return default_config
|
|
64
75
|
|
|
65
76
|
try:
|
|
66
77
|
with open(fallback_config_file, "r", encoding="utf-8") as f:
|
|
67
|
-
|
|
78
|
+
config = yaml.safe_load(f) or default_config
|
|
79
|
+
_validate_config_keys(config, DEFAULT_CONFIG)
|
|
80
|
+
config["language"] = _validate_language(config, languages)
|
|
81
|
+
return config
|
|
68
82
|
except yaml.YAMLError as e:
|
|
69
83
|
log.error("Failed to parse config at %s: %s", fallback_config_file, e)
|
|
84
|
+
default_config["language"] = _validate_language(default_config, languages)
|
|
70
85
|
return default_config
|
|
71
86
|
|
|
72
87
|
|
|
@@ -144,3 +159,30 @@ def get_default_config() -> str:
|
|
|
144
159
|
default_value = config["default"]
|
|
145
160
|
log.info("Default config value: %s", default_value)
|
|
146
161
|
return default_value
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _validate_config_keys(config: dict[str, Any], reference: dict[str, Any]) -> None:
|
|
165
|
+
"""
|
|
166
|
+
Check for missing or extra keys in config.
|
|
167
|
+
"""
|
|
168
|
+
missing_keys = set(reference.keys()) - set(config.keys())
|
|
169
|
+
extra_keys = set(config.keys()) - set(reference.keys())
|
|
170
|
+
|
|
171
|
+
if missing_keys:
|
|
172
|
+
log.warning("Config is missing keys: %s", ", ".join(missing_keys))
|
|
173
|
+
if extra_keys:
|
|
174
|
+
log.error("Config includes unknown keys: %s", ", ".join(extra_keys))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _validate_language(config: dict[str, Any], allowed_languages: set[str]) -> str:
|
|
178
|
+
"""
|
|
179
|
+
Validate that the language code exists in the allowed set.
|
|
180
|
+
Returns the ISO 639-1 code.
|
|
181
|
+
"""
|
|
182
|
+
lang_code = config.get("language")
|
|
183
|
+
if not lang_code or lang_code not in allowed_languages:
|
|
184
|
+
log.warning(
|
|
185
|
+
"Language code '%s' is not supported. Falling back to 'en'.", lang_code
|
|
186
|
+
)
|
|
187
|
+
return "en"
|
|
188
|
+
return lang_code
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Set the language for the AI responses.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
LANGUAGE_MAP = {
|
|
6
|
+
"af": "Afrikaans",
|
|
7
|
+
"am": "Amharic",
|
|
8
|
+
"ar": "Arabic",
|
|
9
|
+
"az": "Azerbaijani",
|
|
10
|
+
"be": "Belarusian",
|
|
11
|
+
"bg": "Bulgarian",
|
|
12
|
+
"bn": "Bengali",
|
|
13
|
+
"bo": "Tibetan",
|
|
14
|
+
"bs": "Bosnian",
|
|
15
|
+
"ca": "Catalan",
|
|
16
|
+
"ce": "Chechen",
|
|
17
|
+
"cs": "Czech",
|
|
18
|
+
"cy": "Welsh",
|
|
19
|
+
"da": "Danish",
|
|
20
|
+
"de": "German",
|
|
21
|
+
"dot": "Dothraki",
|
|
22
|
+
"el": "Greek",
|
|
23
|
+
"en": "English",
|
|
24
|
+
"eo": "Esperanto",
|
|
25
|
+
"es": "Spanish",
|
|
26
|
+
"et": "Estonian",
|
|
27
|
+
"eu": "Basque",
|
|
28
|
+
"fa": "Persian",
|
|
29
|
+
"fi": "Finnish",
|
|
30
|
+
"fo": "Faroese",
|
|
31
|
+
"fr": "French",
|
|
32
|
+
"ga": "Irish",
|
|
33
|
+
"gl": "Galician",
|
|
34
|
+
"gu": "Gujarati",
|
|
35
|
+
"ha": "Hausa",
|
|
36
|
+
"he": "Hebrew",
|
|
37
|
+
"hi": "Hindi",
|
|
38
|
+
"hr": "Croatian",
|
|
39
|
+
"hu": "Hungarian",
|
|
40
|
+
"hy": "Armenian",
|
|
41
|
+
"id": "Indonesian",
|
|
42
|
+
"is": "Icelandic",
|
|
43
|
+
"it": "Italian",
|
|
44
|
+
"ja": "Japanese",
|
|
45
|
+
"jv": "Javanese",
|
|
46
|
+
"ka": "Georgian",
|
|
47
|
+
"kk": "Kazakh",
|
|
48
|
+
"kl": "Kalaallisut",
|
|
49
|
+
"km": "Khmer",
|
|
50
|
+
"kn": "Kannada",
|
|
51
|
+
"ko": "Korean",
|
|
52
|
+
"ku": "Kurdish",
|
|
53
|
+
"la": "Latin",
|
|
54
|
+
"lb": "Luxembourgish",
|
|
55
|
+
"lt": "Lithuanian",
|
|
56
|
+
"lv": "Latvian",
|
|
57
|
+
"mk": "Macedonian",
|
|
58
|
+
"ml": "Malayalam",
|
|
59
|
+
"mn": "Mongolian",
|
|
60
|
+
"ms": "Malay",
|
|
61
|
+
"mr": "Marathi",
|
|
62
|
+
"mt": "Maltese",
|
|
63
|
+
"my": "Burmese",
|
|
64
|
+
"ne": "Nepali",
|
|
65
|
+
"nl": "Dutch",
|
|
66
|
+
"no": "Norwegian",
|
|
67
|
+
"or": "Odia",
|
|
68
|
+
"pa": "Punjabi",
|
|
69
|
+
"pl": "Polish",
|
|
70
|
+
"ps": "Pashto",
|
|
71
|
+
"pt": "Portuguese",
|
|
72
|
+
"ro": "Romanian",
|
|
73
|
+
"ru": "Russian",
|
|
74
|
+
"sd": "Sindhi",
|
|
75
|
+
"si": "Sinhala",
|
|
76
|
+
"sk": "Slovak",
|
|
77
|
+
"sl": "Slovenian",
|
|
78
|
+
"so": "Somali",
|
|
79
|
+
"sq": "Albanian",
|
|
80
|
+
"sr": "Serbian",
|
|
81
|
+
"su": "Sundanese",
|
|
82
|
+
"sv": "Swedish",
|
|
83
|
+
"sw": "Swahili",
|
|
84
|
+
"ta": "Tamil",
|
|
85
|
+
"te": "Telugu",
|
|
86
|
+
"tg": "Tajik",
|
|
87
|
+
"th": "Thai",
|
|
88
|
+
"tlh": "Klingon",
|
|
89
|
+
"tl": "Tagalog",
|
|
90
|
+
"tr": "Turkish",
|
|
91
|
+
"ug": "Uyghur",
|
|
92
|
+
"uk": "Ukrainian",
|
|
93
|
+
"ur": "Urdu",
|
|
94
|
+
"uz": "Uzbek",
|
|
95
|
+
"val": "Valyrian",
|
|
96
|
+
"vi": "Vietnamese",
|
|
97
|
+
"zh": "Chinese",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ALLOWED_LANGUAGES = set(LANGUAGE_MAP.keys())
|