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.
Files changed (48) hide show
  1. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.pylintrc +2 -1
  2. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/Makefile +6 -2
  3. git_cai_cli-0.2.0/PKG-INFO +142 -0
  4. git_cai_cli-0.2.0/README.md +113 -0
  5. git_cai_cli-0.2.0/archlinux/PKGBUILD +23 -0
  6. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/pyproject.toml +7 -4
  7. git_cai_cli-0.2.0/src/git_cai_cli/cli.py +111 -0
  8. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/core/config.py +44 -2
  9. git_cai_cli-0.2.0/src/git_cai_cli/core/languages.py +100 -0
  10. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/core/llm.py +21 -6
  11. git_cai_cli-0.2.0/src/git_cai_cli/core/options.py +178 -0
  12. git_cai_cli-0.2.0/src/git_cai_cli.egg-info/PKG-INFO +142 -0
  13. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli.egg-info/SOURCES.txt +3 -1
  14. git_cai_cli-0.2.0/src/git_cai_cli.egg-info/entry_points.txt +2 -0
  15. git_cai_cli-0.2.0/src/git_cai_cli.egg-info/requires.txt +5 -0
  16. git_cai_cli-0.2.0/src/tests/test_core/test_config.py +267 -0
  17. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/uv.lock +544 -124
  18. git_cai_cli-0.1.1.dev0/PKG-INFO +0 -104
  19. git_cai_cli-0.1.1.dev0/PKGBUILD +0 -36
  20. git_cai_cli-0.1.1.dev0/README.md +0 -77
  21. git_cai_cli-0.1.1.dev0/src/git_cai_cli/cli.py +0 -61
  22. git_cai_cli-0.1.1.dev0/src/git_cai_cli.egg-info/PKG-INFO +0 -104
  23. git_cai_cli-0.1.1.dev0/src/git_cai_cli.egg-info/entry_points.txt +0 -2
  24. git_cai_cli-0.1.1.dev0/src/git_cai_cli.egg-info/requires.txt +0 -3
  25. git_cai_cli-0.1.1.dev0/src/tests/test_core/test_config.py +0 -128
  26. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.caiignore +0 -0
  27. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.gitignore +0 -0
  28. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.bandit.yml +0 -0
  29. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.checkov.yml +0 -0
  30. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.flake8 +0 -0
  31. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.ls-lint.yml +0 -0
  32. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.markdown-link-check.json +0 -0
  33. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.markdownlint.json +0 -0
  34. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.proselintrc +0 -0
  35. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/.yamllint.yml +0 -0
  36. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/check_git_branch_name.sh +0 -0
  37. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/lychee.toml +0 -0
  38. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.linters/pyrightconfig.json +0 -0
  39. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.mega-linter.yml +0 -0
  40. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.semgrepignore +0 -0
  41. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/.trivyignore +0 -0
  42. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/LICENSE +0 -0
  43. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/setup.cfg +0 -0
  44. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/__init__.py +0 -0
  45. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/core/__init__.py +0 -0
  46. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli/core/gitutils.py +0 -0
  47. {git_cai_cli-0.1.1.dev0 → git_cai_cli-0.2.0}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
  48. {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 commits"
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.41.0",
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 :: 3 - Alpha",
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:main"
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
- return yaml.safe_load(f) or default_config
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())