cmt-cli 0.1.1__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 (31) hide show
  1. cmt_cli-0.2.0/.github/workflows/test.yml +27 -0
  2. cmt_cli-0.2.0/PKG-INFO +93 -0
  3. cmt_cli-0.2.0/README.md +69 -0
  4. cmt_cli-0.2.0/art/cmt-cli.svg +1 -0
  5. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/pyproject.toml +3 -2
  6. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/ai/openai.py +5 -12
  7. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/cli.py +47 -7
  8. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/config/settings.py +13 -11
  9. cmt_cli-0.2.0/tests/unit/test_repository.py +24 -0
  10. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/uv.lock +40 -2
  11. cmt_cli-0.1.1/PKG-INFO +0 -31
  12. cmt_cli-0.1.1/README.md +0 -7
  13. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/.gitignore +0 -0
  14. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/.python-version +0 -0
  15. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/LICENSE +0 -0
  16. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/__init__.py +0 -0
  17. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/ai/__init__.py +0 -0
  18. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/ai/cache.py +0 -0
  19. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/ai/prompt.py +0 -0
  20. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/ai/provider.py +0 -0
  21. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/analysis/__init__.py +0 -0
  22. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/analysis/analyzer.py +0 -0
  23. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/config/__init__.py +0 -0
  24. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/exceptions.py +0 -0
  25. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/git/__init__.py +0 -0
  26. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/git/repository.py +0 -0
  27. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/models/__init__.py +0 -0
  28. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/models/changes.py +0 -0
  29. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/models/suggestion.py +0 -0
  30. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/src/cmt/utils.py +0 -0
  31. {cmt_cli-0.1.1 → cmt_cli-0.2.0}/tests/__init__.py +0 -0
@@ -0,0 +1,27 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v3
19
+ with:
20
+ python-version: "3.12"
21
+ enable-cache: true
22
+
23
+ - name: Install dependencies
24
+ run: uv sync --all-groups
25
+
26
+ - name: Run tests
27
+ run: uv run pytest
cmt_cli-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.5
2
+ Name: cmt-cli
3
+ Version: 0.2.0
4
+ Summary: AI-powered Git commit message generator
5
+ Project-URL: Homepage, https://github.com/bijaydas/cmt
6
+ Project-URL: Documentation, https://github.com/bijaydas/cmt#readme
7
+ Project-URL: Repository, https://github.com/bijaydas/cmt
8
+ Project-URL: Issues, https://github.com/bijaydas/cmt/issues
9
+ Project-URL: Changelog, https://github.com/bijaydas/cmt/releases
10
+ Author-email: Bijay Das <me@bijaydas.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Version Control
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: langchain-openai>=1.6.0
20
+ Requires-Dist: langchain>=1.3.18
21
+ Requires-Dist: pydantic>=2.13.4
22
+ Requires-Dist: typer>=0.27.1
23
+ Description-Content-Type: text/markdown
24
+
25
+ <p align="center">
26
+ <img src="art/cmt-cli.svg" alt="cmt-cli logo" width="400">
27
+ </p>
28
+
29
+ # cmt-cli
30
+
31
+ <p>
32
+ <a href="https://pypi.org/project/cmt-cli/"><img src="https://img.shields.io/pypi/v/cmt-cli.svg" alt="PyPI version"></a>
33
+ <a href="https://pypi.org/project/cmt-cli/"><img src="https://img.shields.io/pypi/pyversions/cmt-cli.svg" alt="Python versions"></a>
34
+ <a href="https://github.com/bijaydas/cmt/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
35
+ <img src="https://img.shields.io/badge/status-alpha-orange.svg" alt="Alpha status">
36
+ </p>
37
+
38
+ <p>
39
+ AI-powered CLI tool that analyzes your staged Git changes and suggests a professional commit message.
40
+ </p>
41
+
42
+ > **Note:** `cmt-cli` is in **alpha**. Expect breaking changes, rough edges, and incomplete features until a stable
43
+ > release.
44
+
45
+ ## Features
46
+
47
+ - Analyzes staged Git changes (added, modified, deleted, and renamed files, plus diffs)
48
+ - Generates a commit message and description using OpenAI (via LangChain)
49
+ - Review, edit (in `vim`), or reject the suggested message before committing
50
+ - Caches suggestions per diff/model to avoid redundant API calls
51
+ - Simple configuration for your OpenAI API key and model
52
+ ``
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ uv tool install cmt-cli
58
+ ```
59
+
60
+ Requires Python 3.12+.
61
+
62
+ ## Usage
63
+
64
+ Stage your changes as usual, then run:
65
+
66
+ ```bash
67
+ git add .
68
+ cmt suggest
69
+ ```
70
+
71
+ You'll be shown a suggested commit message and can choose to:
72
+
73
+ - `y` — commit with the suggested message
74
+ - `e` — edit the message in `vim` before committing
75
+ - `n` — abort
76
+
77
+ ### Configuration
78
+
79
+ Before first use, configure your OpenAI API key and model:
80
+
81
+ ```bash
82
+ cmt config set
83
+ ```
84
+
85
+ To view your current configuration:
86
+
87
+ ```bash
88
+ cmt config get
89
+ ```
90
+
91
+ ## License
92
+
93
+ This project is licensed under the terms of the MIT license.
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <img src="art/cmt-cli.svg" alt="cmt-cli logo" width="400">
3
+ </p>
4
+
5
+ # cmt-cli
6
+
7
+ <p>
8
+ <a href="https://pypi.org/project/cmt-cli/"><img src="https://img.shields.io/pypi/v/cmt-cli.svg" alt="PyPI version"></a>
9
+ <a href="https://pypi.org/project/cmt-cli/"><img src="https://img.shields.io/pypi/pyversions/cmt-cli.svg" alt="Python versions"></a>
10
+ <a href="https://github.com/bijaydas/cmt/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
11
+ <img src="https://img.shields.io/badge/status-alpha-orange.svg" alt="Alpha status">
12
+ </p>
13
+
14
+ <p>
15
+ AI-powered CLI tool that analyzes your staged Git changes and suggests a professional commit message.
16
+ </p>
17
+
18
+ > **Note:** `cmt-cli` is in **alpha**. Expect breaking changes, rough edges, and incomplete features until a stable
19
+ > release.
20
+
21
+ ## Features
22
+
23
+ - Analyzes staged Git changes (added, modified, deleted, and renamed files, plus diffs)
24
+ - Generates a commit message and description using OpenAI (via LangChain)
25
+ - Review, edit (in `vim`), or reject the suggested message before committing
26
+ - Caches suggestions per diff/model to avoid redundant API calls
27
+ - Simple configuration for your OpenAI API key and model
28
+ ``
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ uv tool install cmt-cli
34
+ ```
35
+
36
+ Requires Python 3.12+.
37
+
38
+ ## Usage
39
+
40
+ Stage your changes as usual, then run:
41
+
42
+ ```bash
43
+ git add .
44
+ cmt suggest
45
+ ```
46
+
47
+ You'll be shown a suggested commit message and can choose to:
48
+
49
+ - `y` — commit with the suggested message
50
+ - `e` — edit the message in `vim` before committing
51
+ - `n` — abort
52
+
53
+ ### Configuration
54
+
55
+ Before first use, configure your OpenAI API key and model:
56
+
57
+ ```bash
58
+ cmt config set
59
+ ```
60
+
61
+ To view your current configuration:
62
+
63
+ ```bash
64
+ cmt config get
65
+ ```
66
+
67
+ ## License
68
+
69
+ This project is licensed under the terms of the MIT license.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1584" zoomAndPan="magnify" viewBox="0 0 1188 296.999993" height="396" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><g/><clipPath id="8fdfe6cc69"><rect x="0" width="344" y="0" height="139"/></clipPath></defs><g transform="matrix(1, 0, 0, 1, 420, 79)"><g clip-path="url(#8fdfe6cc69)"><g fill="#3d3b3a" fill-opacity="1"><g transform="translate(0.871423, 99.480686)"><g><path d="M 36.5625 -42.0625 C 32.90625 -42.0625 29.757812 -40.707031 27.125 -38 C 24.488281 -35.289062 23.171875 -32.03125 23.171875 -28.21875 C 23.171875 -24.34375 24.488281 -21.050781 27.125 -18.34375 C 29.757812 -15.632812 32.90625 -14.28125 36.5625 -14.28125 C 38.320312 -14.28125 39.988281 -14.5 41.5625 -14.9375 C 43.132812 -15.375 44.285156 -15.8125 45.015625 -16.25 L 46.015625 -16.90625 L 52.59375 -3.40625 C 52.082031 -3.039062 51.367188 -2.582031 50.453125 -2.03125 C 49.546875 -1.476562 47.460938 -0.726562 44.203125 0.21875 C 40.941406 1.164062 37.445312 1.640625 33.71875 1.640625 C 25.4375 1.640625 18.164062 -1.265625 11.90625 -7.078125 C 5.65625 -12.898438 2.53125 -19.875 2.53125 -28 C 2.53125 -36.195312 5.65625 -43.242188 11.90625 -49.140625 C 18.164062 -55.035156 25.4375 -57.984375 33.71875 -57.984375 C 37.445312 -57.984375 40.882812 -57.539062 44.03125 -56.65625 C 47.175781 -55.78125 49.410156 -54.90625 50.734375 -54.03125 L 52.59375 -52.703125 L 46.015625 -39.421875 C 43.597656 -41.179688 40.445312 -42.0625 36.5625 -42.0625 Z M 36.5625 -42.0625 "/></g></g></g><g fill="#3d3b3a" fill-opacity="1"><g transform="translate(50.17971, 99.480686)"><g><path d="M 6.59375 0 L 6.59375 -56.328125 L 26.03125 -56.328125 L 26.03125 -50.40625 L 26.25 -50.40625 C 31.300781 -55.457031 36.679688 -57.984375 42.390625 -57.984375 C 46.484375 -57.984375 50.320312 -57.140625 53.90625 -55.453125 C 57.5 -53.765625 60.210938 -51.164062 62.046875 -47.65625 C 67.753906 -54.539062 73.976562 -57.984375 80.71875 -57.984375 C 86.71875 -57.984375 91.875 -56.296875 96.1875 -52.921875 C 100.507812 -49.554688 102.671875 -44.285156 102.671875 -37.109375 L 102.671875 0 L 83.234375 0 L 83.234375 -31.84375 C 83.234375 -34.84375 82.410156 -37.347656 80.765625 -39.359375 C 79.117188 -41.378906 76.648438 -42.390625 73.359375 -42.390625 C 70.503906 -42.390625 68.320312 -41.523438 66.8125 -39.796875 C 65.3125 -38.078125 64.488281 -36.015625 64.34375 -33.609375 L 64.34375 0 L 44.90625 0 L 44.90625 -31.84375 C 44.90625 -34.84375 44.082031 -37.347656 42.4375 -39.359375 C 40.789062 -41.378906 38.320312 -42.390625 35.03125 -42.390625 C 32.03125 -42.390625 29.78125 -41.453125 28.28125 -39.578125 C 26.78125 -37.710938 26.03125 -35.5 26.03125 -32.9375 L 26.03125 0 Z M 6.59375 0 "/></g></g></g><g fill="#3d3b3a" fill-opacity="1"><g transform="translate(153.298845, 99.480686)"><g><path d="M 9.890625 -56.328125 L 9.890625 -77.3125 L 29.3125 -77.3125 L 29.3125 -56.328125 L 41.171875 -56.328125 L 41.171875 -41.515625 L 29.3125 -41.515625 L 29.3125 -23.609375 C 29.3125 -18.410156 30.78125 -15.8125 33.71875 -15.8125 C 34.445312 -15.8125 35.210938 -15.957031 36.015625 -16.25 C 36.816406 -16.539062 37.441406 -16.832031 37.890625 -17.125 L 38.546875 -17.5625 L 43.375 -1.859375 C 39.195312 0.472656 34.4375 1.640625 29.09375 1.640625 C 25.4375 1.640625 22.289062 0.996094 19.65625 -0.28125 C 17.019531 -1.5625 15.023438 -3.28125 13.671875 -5.4375 C 12.316406 -7.59375 11.347656 -9.859375 10.765625 -12.234375 C 10.179688 -14.617188 9.890625 -17.164062 9.890625 -19.875 L 9.890625 -41.515625 L 1.75 -41.515625 L 1.75 -56.328125 Z M 9.890625 -56.328125 "/></g></g></g><g fill="#3d3b3a" fill-opacity="1"><g transform="translate(192.284217, 99.480686)"><g><path d="M 6.59375 -40.1875 L 41.953125 -40.1875 L 41.953125 -23.390625 L 6.59375 -23.390625 Z M 6.59375 -40.1875 "/></g></g></g><g fill="#3d3b3a" fill-opacity="1"><g transform="translate(236.431027, 99.480686)"><g><path d="M 36.5625 -42.0625 C 32.90625 -42.0625 29.757812 -40.707031 27.125 -38 C 24.488281 -35.289062 23.171875 -32.03125 23.171875 -28.21875 C 23.171875 -24.34375 24.488281 -21.050781 27.125 -18.34375 C 29.757812 -15.632812 32.90625 -14.28125 36.5625 -14.28125 C 38.320312 -14.28125 39.988281 -14.5 41.5625 -14.9375 C 43.132812 -15.375 44.285156 -15.8125 45.015625 -16.25 L 46.015625 -16.90625 L 52.59375 -3.40625 C 52.082031 -3.039062 51.367188 -2.582031 50.453125 -2.03125 C 49.546875 -1.476562 47.460938 -0.726562 44.203125 0.21875 C 40.941406 1.164062 37.445312 1.640625 33.71875 1.640625 C 25.4375 1.640625 18.164062 -1.265625 11.90625 -7.078125 C 5.65625 -12.898438 2.53125 -19.875 2.53125 -28 C 2.53125 -36.195312 5.65625 -43.242188 11.90625 -49.140625 C 18.164062 -55.035156 25.4375 -57.984375 33.71875 -57.984375 C 37.445312 -57.984375 40.882812 -57.539062 44.03125 -56.65625 C 47.175781 -55.78125 49.410156 -54.90625 50.734375 -54.03125 L 52.59375 -52.703125 L 46.015625 -39.421875 C 43.597656 -41.179688 40.445312 -42.0625 36.5625 -42.0625 Z M 36.5625 -42.0625 "/></g></g></g><g fill="#3d3b3a" fill-opacity="1"><g transform="translate(285.739315, 99.480686)"><g><path d="M 6.59375 0 L 6.59375 -94.4375 L 26.03125 -94.4375 L 26.03125 0 Z M 6.59375 0 "/></g></g></g><g fill="#3d3b3a" fill-opacity="1"><g transform="translate(313.962521, 99.480686)"><g><path d="M 8.453125 -87.734375 C 10.722656 -90.003906 13.429688 -91.140625 16.578125 -91.140625 C 19.722656 -91.140625 22.429688 -90.003906 24.703125 -87.734375 C 26.972656 -85.472656 28.109375 -82.765625 28.109375 -79.609375 C 28.109375 -76.460938 26.972656 -73.753906 24.703125 -71.484375 C 22.429688 -69.210938 19.722656 -68.078125 16.578125 -68.078125 C 13.429688 -68.078125 10.722656 -69.210938 8.453125 -71.484375 C 6.179688 -73.753906 5.046875 -76.460938 5.046875 -79.609375 C 5.046875 -82.765625 6.179688 -85.472656 8.453125 -87.734375 Z M 6.59375 -56.328125 L 6.59375 0 L 26.03125 0 L 26.03125 -56.328125 Z M 6.59375 -56.328125 "/></g></g></g></g></g></svg>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cmt-cli"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "AI-powered Git commit message generator"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -40,6 +40,7 @@ keywords = ["ai", "git", "commit", "message", "generator"]
40
40
 
41
41
  [dependency-groups]
42
42
  dev = [
43
+ "pytest>=9.1.1",
43
44
  "ruff>=0.16.5",
44
45
  ]
45
46
  [tool.ruff]
@@ -62,4 +63,4 @@ ignore = []
62
63
  quote-style = "double"
63
64
 
64
65
  [tool.hatch.build.targets.wheel]
65
- packages = ["src/cmt"]
66
+ packages = ["src/cmt"]
@@ -15,11 +15,7 @@ class OpenAIProvider(AIProvider):
15
15
  settings = Settings()
16
16
  self.config = settings.get()
17
17
 
18
- def _build_prompt(
19
- self,
20
- changes: StagedChangeSet,
21
- analysis: AnalysisResult
22
- ) -> str:
18
+ def _build_prompt(self, changes: StagedChangeSet, analysis: AnalysisResult) -> str:
23
19
  staged_files = self._process_files(changes.files)
24
20
  staged_diffs = changes.diff
25
21
 
@@ -30,7 +26,7 @@ class OpenAIProvider(AIProvider):
30
26
  deleted_files=analysis.deleted_files,
31
27
  renamed_files=analysis.renamed_files,
32
28
  staged_files=staged_files,
33
- staged_diffs=staged_diffs
29
+ staged_diffs=staged_diffs,
34
30
  )
35
31
 
36
32
  @staticmethod
@@ -60,9 +56,7 @@ class OpenAIProvider(AIProvider):
60
56
  return result["structured_response"]
61
57
 
62
58
  def generate_commit_message(
63
- self,
64
- change_set: StagedChangeSet,
65
- analysis: AnalysisResult
59
+ self, change_set: StagedChangeSet, analysis: AnalysisResult
66
60
  ) -> CommitSuggestion:
67
61
  prompt = self._build_prompt(change_set, analysis)
68
62
 
@@ -71,8 +65,7 @@ class OpenAIProvider(AIProvider):
71
65
 
72
66
  if cached_message:
73
67
  return CommitSuggestion(
74
- message=cached_message.message,
75
- description=cached_message.description
68
+ message=cached_message.message, description=cached_message.description
76
69
  )
77
70
 
78
71
  suggestion = self._invoke(prompt)
@@ -83,4 +76,4 @@ class OpenAIProvider(AIProvider):
83
76
 
84
77
  @staticmethod
85
78
  def commit_command(commit_suggestion: CommitSuggestion) -> str:
86
- return f"{commit_suggestion.message} \n\n{commit_suggestion.description}"
79
+ return f"{commit_suggestion.message} \n\n{commit_suggestion.description}"
@@ -1,17 +1,39 @@
1
+ from enum import StrEnum
2
+ from importlib.metadata import version
3
+
1
4
  import typer
2
5
 
3
6
  from cmt.ai.openai import OpenAIProvider
4
7
  from cmt.analysis.analyzer import Analyzer
5
- from cmt.config.settings import OpenAIConfig, Settings
8
+ from cmt.config.settings import Config, Settings
6
9
  from cmt.exceptions import CmtError
7
10
  from cmt.git.repository import Repository
8
11
  from cmt.utils import edit_with_vim
9
12
 
10
- app = typer.Typer()
13
+ app = typer.Typer(
14
+ add_completion=False,
15
+ )
16
+
17
+
18
+ def version_callback(value: bool) -> None:
19
+ if value:
20
+ typer.echo(f"cmt-cli version: {version('cmt-cli')}")
21
+ raise typer.Exit()
22
+
23
+
24
+ @app.callback(invoke_without_command=True)
25
+ def main(
26
+ ctx: typer.Context,
27
+ flag: bool = typer.Option(False, "--version", callback=version_callback, is_eager=True),
28
+ ) -> None:
29
+ if ctx.invoked_subcommand is None:
30
+ typer.echo(ctx.get_help())
31
+ raise typer.Exit(code=0)
11
32
 
12
33
 
13
34
  @app.command()
14
35
  def suggest() -> None:
36
+ """Suggest a commit message based on staged changes."""
15
37
  try:
16
38
  repository = Repository()
17
39
  analyzer = Analyzer()
@@ -38,7 +60,7 @@ def suggest() -> None:
38
60
 
39
61
  while True:
40
62
  action = (
41
- typer.prompt("\nUse this message? [y]es / [e]dit / [n]o", default="y")
63
+ typer.prompt("Use this message? [y]es / [e]dit / [n]o", default="y")
42
64
  .strip()
43
65
  .lower()[0]
44
66
  )
@@ -69,16 +91,34 @@ def suggest() -> None:
69
91
  raise typer.Exit(code=1) from None
70
92
 
71
93
 
94
+ class ConfigTask(StrEnum):
95
+ set = "set"
96
+ get = "get"
97
+
98
+
72
99
  @app.command()
73
- def config(task: str) -> None:
74
- if task == "set":
100
+ def config(task: ConfigTask) -> None:
101
+ """Configure cmt-cli configuration for OpenAI API key and model."""
102
+ if task == ConfigTask.set:
75
103
  settings = Settings()
76
104
 
77
- open_ai_key = typer.prompt("Enter your OpenAI API key")
105
+ open_ai_key = typer.prompt("Enter your OpenAI API key", default=None)
78
106
  open_ai_model = typer.prompt(
79
107
  "Enter your OpenAI model", default=settings.OPEN_AI_DEFAULT_MODEL
80
108
  )
81
- settings.set(OpenAIConfig(api_key=open_ai_key, model=open_ai_model))
109
+ settings.set(Config(api_key=open_ai_key, model=open_ai_model))
110
+
111
+ if task == ConfigTask.get:
112
+ settings = Settings()
113
+ setting_values = settings.get()
114
+
115
+ if setting_values.api_key:
116
+ typer.echo(f"OpenAI API key: {setting_values.api_key[:10]} ****")
117
+
118
+ if setting_values.model:
119
+ typer.echo(f"OpenAI model: {setting_values.model}")
120
+
121
+ raise typer.Exit(code=0)
82
122
 
83
123
 
84
124
  if __name__ == "__main__":
@@ -6,7 +6,7 @@ from pydantic import BaseModel
6
6
  from cmt.exceptions import ConfigurationError
7
7
 
8
8
 
9
- class OpenAIConfig(BaseModel):
9
+ class Config(BaseModel):
10
10
  api_key: str
11
11
  model: str
12
12
 
@@ -18,19 +18,24 @@ class Settings:
18
18
  CONFIG_FILE: Path = CONFIG_DIR / "config.ini"
19
19
  OPEN_AI_DEFAULT_MODEL: str = "gpt-4o-mini"
20
20
 
21
- def set(self, openai_config: OpenAIConfig):
21
+ def set(self, openai_config: Config):
22
22
  self.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
23
23
 
24
24
  config = ConfigParser()
25
- config["openai"] = {
26
- "api_key": openai_config.api_key,
27
- "model": openai_config.model
28
- }
25
+
26
+ if openai_config.api_key:
27
+ config["openai"] = {}
28
+
29
+ if openai_config.api_key:
30
+ config["openai"]["api_key"] = openai_config.api_key
31
+
32
+ if openai_config.model:
33
+ config["openai"]["model"] = openai_config.model
29
34
 
30
35
  with open(self.CONFIG_FILE, "w") as f:
31
36
  config.write(f)
32
37
 
33
- def get(self) -> OpenAIConfig:
38
+ def get(self) -> Config:
34
39
  if not self.CONFIG_FILE.exists():
35
40
  raise FileNotFoundError(
36
41
  "Configuration file not found. Run `cmt config set` to set it up."
@@ -52,7 +57,4 @@ class Settings:
52
57
  "Model not found in the configuration. Run `cmt config set` to set it up."
53
58
  )
54
59
 
55
- return OpenAIConfig(
56
- api_key=api_key,
57
- model=model
58
- )
60
+ return Config(api_key=api_key, model=model)
@@ -0,0 +1,24 @@
1
+ from cmt.git.repository import Repository
2
+
3
+
4
+ def test_invalid_git_repository(tmp_path):
5
+ repo_path = tmp_path / "test_repo"
6
+ repo_path.mkdir()
7
+
8
+ repo = Repository(tmp_path)
9
+
10
+ assert repo.is_git_repository() is False
11
+
12
+
13
+ def test_valid_git_repository(tmp_path):
14
+ repo_path = tmp_path / "test_repo"
15
+ repo_path.mkdir()
16
+
17
+ # Initialize a git repository
18
+ import subprocess
19
+
20
+ subprocess.run(["git", "init"], cwd=repo_path)
21
+
22
+ repo = Repository(repo_path)
23
+
24
+ assert repo.is_git_repository() is True
@@ -175,7 +175,7 @@ wheels = [
175
175
 
176
176
  [[package]]
177
177
  name = "cmt-cli"
178
- version = "0.1.0"
178
+ version = "0.2.0"
179
179
  source = { editable = "." }
180
180
  dependencies = [
181
181
  { name = "langchain" },
@@ -186,6 +186,7 @@ dependencies = [
186
186
 
187
187
  [package.dev-dependencies]
188
188
  dev = [
189
+ { name = "pytest" },
189
190
  { name = "ruff" },
190
191
  ]
191
192
 
@@ -198,7 +199,10 @@ requires-dist = [
198
199
  ]
199
200
 
200
201
  [package.metadata.requires-dev]
201
- dev = [{ name = "ruff", specifier = ">=0.16.5" }]
202
+ dev = [
203
+ { name = "pytest", specifier = ">=9.1.1" },
204
+ { name = "ruff", specifier = ">=0.16.5" },
205
+ ]
202
206
 
203
207
  [[package]]
204
208
  name = "colorama"
@@ -303,6 +307,15 @@ wheels = [
303
307
  { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
304
308
  ]
305
309
 
310
+ [[package]]
311
+ name = "iniconfig"
312
+ version = "2.3.0"
313
+ source = { registry = "https://pypi.org/simple" }
314
+ sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
315
+ wheels = [
316
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
317
+ ]
318
+
306
319
  [[package]]
307
320
  name = "jiter"
308
321
  version = "0.16.0"
@@ -676,6 +689,15 @@ wheels = [
676
689
  { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
677
690
  ]
678
691
 
692
+ [[package]]
693
+ name = "pluggy"
694
+ version = "1.6.0"
695
+ source = { registry = "https://pypi.org/simple" }
696
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
697
+ wheels = [
698
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
699
+ ]
700
+
679
701
  [[package]]
680
702
  name = "pydantic"
681
703
  version = "2.13.4"
@@ -775,6 +797,22 @@ wheels = [
775
797
  { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
776
798
  ]
777
799
 
800
+ [[package]]
801
+ name = "pytest"
802
+ version = "9.1.1"
803
+ source = { registry = "https://pypi.org/simple" }
804
+ dependencies = [
805
+ { name = "colorama", marker = "sys_platform == 'win32'" },
806
+ { name = "iniconfig" },
807
+ { name = "packaging" },
808
+ { name = "pluggy" },
809
+ { name = "pygments" },
810
+ ]
811
+ sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
812
+ wheels = [
813
+ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
814
+ ]
815
+
778
816
  [[package]]
779
817
  name = "pyyaml"
780
818
  version = "6.0.3"
cmt_cli-0.1.1/PKG-INFO DELETED
@@ -1,31 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: cmt-cli
3
- Version: 0.1.1
4
- Summary: AI-powered Git commit message generator
5
- Project-URL: Homepage, https://github.com/bijaydas/cmt
6
- Project-URL: Documentation, https://github.com/bijaydas/cmt#readme
7
- Project-URL: Repository, https://github.com/bijaydas/cmt
8
- Project-URL: Issues, https://github.com/bijaydas/cmt/issues
9
- Project-URL: Changelog, https://github.com/bijaydas/cmt/releases
10
- Author-email: Bijay Das <me@bijaydas.com>
11
- License: MIT
12
- License-File: LICENSE
13
- Classifier: Development Status :: 3 - Alpha
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Topic :: Software Development :: Version Control
18
- Requires-Python: >=3.12
19
- Requires-Dist: langchain-openai>=1.6.0
20
- Requires-Dist: langchain>=1.3.18
21
- Requires-Dist: pydantic>=2.13.4
22
- Requires-Dist: typer>=0.27.1
23
- Description-Content-Type: text/markdown
24
-
25
- # cmt
26
-
27
- `cmt` is a CLI tool that checks the currently staged Git changes and suggests a professional commit message.
28
-
29
- ## License
30
-
31
- MIT
cmt_cli-0.1.1/README.md DELETED
@@ -1,7 +0,0 @@
1
- # cmt
2
-
3
- `cmt` is a CLI tool that checks the currently staged Git changes and suggests a professional commit message.
4
-
5
- ## License
6
-
7
- MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes