devgen-cli 0.2.1__tar.gz → 0.2.2__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 (54) hide show
  1. devgen_cli-0.2.2/PKG-INFO +177 -0
  2. devgen_cli-0.2.2/README.md +140 -0
  3. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/commit.py +1 -1
  4. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/modules/changelog_generator.py +8 -20
  5. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/modules/commit_generator.py +101 -50
  6. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/providers/__init__.py +10 -7
  7. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/utils.py +47 -8
  8. devgen_cli-0.2.2/devgen_cli.egg-info/PKG-INFO +177 -0
  9. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/pyproject.toml +1 -1
  10. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/setup.py +1 -1
  11. devgen_cli-0.2.1/PKG-INFO +0 -299
  12. devgen_cli-0.2.1/README.md +0 -262
  13. devgen_cli-0.2.1/devgen_cli.egg-info/PKG-INFO +0 -299
  14. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/LICENSE +0 -0
  15. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/MANIFEST.in +0 -0
  16. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/__init__.py +0 -0
  17. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/ai.py +0 -0
  18. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/__init__.py +0 -0
  19. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/changelog.py +0 -0
  20. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/config.py +0 -0
  21. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/gitignore.py +0 -0
  22. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/license.py +0 -0
  23. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/main.py +0 -0
  24. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/release.py +0 -0
  25. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/cli/setup.py +0 -0
  26. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/modules/__init__.py +0 -0
  27. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/modules/gitignore_generator.py +0 -0
  28. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/modules/license_generator.py +0 -0
  29. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/modules/release_note_generator.py +0 -0
  30. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/providers/anthropic.py +0 -0
  31. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/providers/gemini.py +0 -0
  32. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/providers/huggingface.py +0 -0
  33. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/providers/openai.py +0 -0
  34. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/providers/openrouter.py +0 -0
  35. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/commit/commit_message.j2 +0 -0
  36. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/agpl-3.0.json +0 -0
  37. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/apache-2.0.json +0 -0
  38. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/bsd-2-clause.json +0 -0
  39. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/bsd-3-clause.json +0 -0
  40. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/bsl-1.0.json +0 -0
  41. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/cc0-1.0.json +0 -0
  42. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/epl-2.0.json +0 -0
  43. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/gpl-2.0.json +0 -0
  44. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/gpl-3.0.json +0 -0
  45. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/lgpl-2.1.json +0 -0
  46. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/mit.json +0 -0
  47. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/mpl-2.0.json +0 -0
  48. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen/templates/licenses/unlicense.json +0 -0
  49. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen_cli.egg-info/SOURCES.txt +0 -0
  50. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen_cli.egg-info/dependency_links.txt +0 -0
  51. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen_cli.egg-info/entry_points.txt +0 -0
  52. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen_cli.egg-info/requires.txt +0 -0
  53. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/devgen_cli.egg-info/top_level.txt +0 -0
  54. {devgen_cli-0.2.1 → devgen_cli-0.2.2}/setup.cfg +0 -0
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: devgen-cli
3
+ Version: 0.2.2
4
+ Summary: A collection of developer tools
5
+ Home-page: https://github.com/S4NKALP/devgen
6
+ Author: Sankalp Tharu
7
+ Author-email: Sankalp Tharu <sankalptharu50028@gmail.com>
8
+ License: GPL-3.0-or-later
9
+ Project-URL: Homepage, https://github.com/S4NKALP/devgen
10
+ Keywords: devgen,cli,git,changelog,gitignore,license,commit-generator,commit
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: anthropic>=0.75.0
24
+ Requires-Dist: google-generativeai>=0.8.5
25
+ Requires-Dist: jinja2>=3.1.6
26
+ Requires-Dist: openai>=2.11.0
27
+ Requires-Dist: pyyaml>=6.0.3
28
+ Requires-Dist: questionary>=2.1.1
29
+ Requires-Dist: requests>=2.32.5
30
+ Requires-Dist: rich>=14.2.0
31
+ Requires-Dist: toml>=0.10.2
32
+ Requires-Dist: typer>=0.20.0
33
+ Dynamic: author
34
+ Dynamic: home-page
35
+ Dynamic: license-file
36
+ Dynamic: requires-python
37
+
38
+ # DevGen
39
+
40
+ <div align="center">
41
+
42
+ > **Your AI Powerhouse for Git & Project Management.**
43
+ > Stop wasting time on repetitive tasks. Automate your commits, changelogs, and project essentials with a single CLI.
44
+ >
45
+ > PyPI didn't allow the original name, so you'll find it as **devgen-cli** on PyPI
46
+
47
+ <a href="https://pypi.org/project/devgen-cli"><img src="https://img.shields.io/pypi/v/devgen-cli?color=blue&label=PyPI&logo=pypi&logoColor=white" alt="PyPI"></a>
48
+ <img src="https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white" alt="Python">
49
+ <a href="https://github.com/S4NKALP/DevGen/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-GPL--3.0--or--later-blue.svg" alt="License"></a>
50
+
51
+ </div>
52
+
53
+ ---
54
+
55
+ **DevGen** isn't just another CLI tool it's your development workflow on steroids. By leveraging state of the art AI models, DevGen turns tedious manual tasks into one click magic. From semantic commit messages to comprehensive release notes, DevGen handles the boring stuff so you can focus on building.
56
+
57
+ ## ⚡ Why DevGen?
58
+
59
+ **🧠 AI Brain**
60
+ Semantic commit messages powered by Gemini, OpenAI, Claude, HuggingFace, and OpenRouter. It reads your diffs and understands your code.
61
+
62
+ **🚀 Battle Tested**
63
+ Generates **Conventional Commits** and **Semantic Versioning** compliant changelogs that actually make sense.
64
+
65
+ **⚡ Lightning Fast**
66
+ Smart caching and async operations mean you never wait longer than necessary.
67
+
68
+ **Project Essentials**
69
+ Quickly add `.gitignore` and license files to your existing projects. Access cached templates instantly, even without internet.
70
+
71
+ **🛠️ Zero Friction**
72
+ Interactive setup gets you running in seconds.
73
+
74
+ ## 📦 Installation
75
+
76
+ Get started in seconds.
77
+
78
+ ```bash
79
+ # Recommended: Install via pipx for an isolated environment
80
+ pipx install devgen-cli
81
+
82
+ # Or use uv for blazing speed
83
+ uv tool install devgen-cli
84
+
85
+ # Standard pip install
86
+ pip install devgen-cli
87
+ ```
88
+
89
+ ## 🚀 Quick Start
90
+
91
+ **1. Initialize & Configure**
92
+ Tell DevGen which AI provider to use.
93
+
94
+ ```bash
95
+ devgen setup config
96
+ ```
97
+
98
+ **2. Stage & Commit**
99
+ Stage your files and let AI write the message.
100
+
101
+ ```bash
102
+ git add .
103
+ devgen commit run
104
+ ```
105
+
106
+ _Boom. Done._
107
+
108
+ ## 💡 Feature Deep Dive
109
+
110
+ ### 🤖 AI Powered Commits
111
+
112
+ Stop writing "fix bug" or "wip". DevGen analyzes your staged changes, groups them by component, and generates meaningful, semantic commit messages.
113
+
114
+ ```bash
115
+ # Preview what DevGen will generate
116
+ devgen commit run --dry-run
117
+
118
+ # Commit and push in one go
119
+ devgen commit run --push
120
+ ```
121
+
122
+ ### 📝 Changelogs & Release Notes
123
+
124
+ Turn your git history into beautiful, readable documentation.
125
+
126
+ ```bash
127
+ # Generate a changelog from the last tag
128
+ devgen changelog generate
129
+
130
+ # Create release notes for v2.0.0
131
+ devgen release notes --version 2.0.0
132
+ ```
133
+
134
+ ### 🛡️ Essential Files
135
+
136
+ Don't waste time searching for templates. Generate the essential files for your project instantly.
137
+
138
+ ```bash
139
+ # Interactive search for gitignore templates
140
+ devgen gitignore generate
141
+
142
+ # Generate a license interactively
143
+ devgen license generate
144
+ ```
145
+
146
+ ## ⚙️ Configuration
147
+
148
+ Your settings live in `~/.devgen.yaml`. You can tweak your AI provider, model, and preferences there.
149
+
150
+ | Option | Description |
151
+ | :--------- | :----------------------------------------------------------- |
152
+ | `provider` | `gemini`, `openai`, `anthropic`, `huggingface`, `openrouter` |
153
+ | `model` | Specific model name (e.g., `gemini-2.5-flash`, `gpt-4o`) |
154
+ | `emoji` | Enable/disable gitmojis in commits (`true`/`false`) |
155
+
156
+ ## 🤝 Contributing
157
+
158
+ We love contributions! Found a bug? Want a new feature? Open an issue or submit a PR.
159
+
160
+ ## � Acknowledgments
161
+
162
+ DevGen wouldn't be possible without these amazing open-source projects and AI providers:
163
+
164
+ - **[Typer](https://typer.tiangolo.com/)** & **[Rich](https://rich.readthedocs.io/)** for building the beautiful, intuitive, and responsive CLI interface.
165
+ - **[Questionary](https://github.com/tmbo/questionary)** for creating interactive, user-friendly prompts and selection menus.
166
+ - **[Jinja2](https://jinja.palletsprojects.com/)** for the powerful template engine used to generate files and messages.
167
+ - **[Google Gemini](https://deepmind.google/technologies/gemini/)**, **[OpenAI](https://openai.com/)**, **[Anthropic](https://www.anthropic.com/)**, **[HuggingFace](https://huggingface.co/)**, and **[OpenRouter](https://openrouter.ai/)** for providing the advanced AI models that power the semantic generation features.
168
+
169
+ ## �📝 License
170
+
171
+ Proudly open source under the [GPL-3.0-or-later](LICENSE) License.
172
+
173
+ ---
174
+
175
+ <div align="center">
176
+ Made with ❤️ by <a href="https://github.com/S4NKALP">Sankalp</a>
177
+ </div>
@@ -0,0 +1,140 @@
1
+ # DevGen
2
+
3
+ <div align="center">
4
+
5
+ > **Your AI Powerhouse for Git & Project Management.**
6
+ > Stop wasting time on repetitive tasks. Automate your commits, changelogs, and project essentials with a single CLI.
7
+ >
8
+ > PyPI didn't allow the original name, so you'll find it as **devgen-cli** on PyPI
9
+
10
+ <a href="https://pypi.org/project/devgen-cli"><img src="https://img.shields.io/pypi/v/devgen-cli?color=blue&label=PyPI&logo=pypi&logoColor=white" alt="PyPI"></a>
11
+ <img src="https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white" alt="Python">
12
+ <a href="https://github.com/S4NKALP/DevGen/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-GPL--3.0--or--later-blue.svg" alt="License"></a>
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ **DevGen** isn't just another CLI tool it's your development workflow on steroids. By leveraging state of the art AI models, DevGen turns tedious manual tasks into one click magic. From semantic commit messages to comprehensive release notes, DevGen handles the boring stuff so you can focus on building.
19
+
20
+ ## ⚡ Why DevGen?
21
+
22
+ **🧠 AI Brain**
23
+ Semantic commit messages powered by Gemini, OpenAI, Claude, HuggingFace, and OpenRouter. It reads your diffs and understands your code.
24
+
25
+ **🚀 Battle Tested**
26
+ Generates **Conventional Commits** and **Semantic Versioning** compliant changelogs that actually make sense.
27
+
28
+ **⚡ Lightning Fast**
29
+ Smart caching and async operations mean you never wait longer than necessary.
30
+
31
+ **Project Essentials**
32
+ Quickly add `.gitignore` and license files to your existing projects. Access cached templates instantly, even without internet.
33
+
34
+ **🛠️ Zero Friction**
35
+ Interactive setup gets you running in seconds.
36
+
37
+ ## 📦 Installation
38
+
39
+ Get started in seconds.
40
+
41
+ ```bash
42
+ # Recommended: Install via pipx for an isolated environment
43
+ pipx install devgen-cli
44
+
45
+ # Or use uv for blazing speed
46
+ uv tool install devgen-cli
47
+
48
+ # Standard pip install
49
+ pip install devgen-cli
50
+ ```
51
+
52
+ ## 🚀 Quick Start
53
+
54
+ **1. Initialize & Configure**
55
+ Tell DevGen which AI provider to use.
56
+
57
+ ```bash
58
+ devgen setup config
59
+ ```
60
+
61
+ **2. Stage & Commit**
62
+ Stage your files and let AI write the message.
63
+
64
+ ```bash
65
+ git add .
66
+ devgen commit run
67
+ ```
68
+
69
+ _Boom. Done._
70
+
71
+ ## 💡 Feature Deep Dive
72
+
73
+ ### 🤖 AI Powered Commits
74
+
75
+ Stop writing "fix bug" or "wip". DevGen analyzes your staged changes, groups them by component, and generates meaningful, semantic commit messages.
76
+
77
+ ```bash
78
+ # Preview what DevGen will generate
79
+ devgen commit run --dry-run
80
+
81
+ # Commit and push in one go
82
+ devgen commit run --push
83
+ ```
84
+
85
+ ### 📝 Changelogs & Release Notes
86
+
87
+ Turn your git history into beautiful, readable documentation.
88
+
89
+ ```bash
90
+ # Generate a changelog from the last tag
91
+ devgen changelog generate
92
+
93
+ # Create release notes for v2.0.0
94
+ devgen release notes --version 2.0.0
95
+ ```
96
+
97
+ ### 🛡️ Essential Files
98
+
99
+ Don't waste time searching for templates. Generate the essential files for your project instantly.
100
+
101
+ ```bash
102
+ # Interactive search for gitignore templates
103
+ devgen gitignore generate
104
+
105
+ # Generate a license interactively
106
+ devgen license generate
107
+ ```
108
+
109
+ ## ⚙️ Configuration
110
+
111
+ Your settings live in `~/.devgen.yaml`. You can tweak your AI provider, model, and preferences there.
112
+
113
+ | Option | Description |
114
+ | :--------- | :----------------------------------------------------------- |
115
+ | `provider` | `gemini`, `openai`, `anthropic`, `huggingface`, `openrouter` |
116
+ | `model` | Specific model name (e.g., `gemini-2.5-flash`, `gpt-4o`) |
117
+ | `emoji` | Enable/disable gitmojis in commits (`true`/`false`) |
118
+
119
+ ## 🤝 Contributing
120
+
121
+ We love contributions! Found a bug? Want a new feature? Open an issue or submit a PR.
122
+
123
+ ## � Acknowledgments
124
+
125
+ DevGen wouldn't be possible without these amazing open-source projects and AI providers:
126
+
127
+ - **[Typer](https://typer.tiangolo.com/)** & **[Rich](https://rich.readthedocs.io/)** for building the beautiful, intuitive, and responsive CLI interface.
128
+ - **[Questionary](https://github.com/tmbo/questionary)** for creating interactive, user-friendly prompts and selection menus.
129
+ - **[Jinja2](https://jinja.palletsprojects.com/)** for the powerful template engine used to generate files and messages.
130
+ - **[Google Gemini](https://deepmind.google/technologies/gemini/)**, **[OpenAI](https://openai.com/)**, **[Anthropic](https://www.anthropic.com/)**, **[HuggingFace](https://huggingface.co/)**, and **[OpenRouter](https://openrouter.ai/)** for providing the advanced AI models that power the semantic generation features.
131
+
132
+ ## �📝 License
133
+
134
+ Proudly open source under the [GPL-3.0-or-later](LICENSE) License.
135
+
136
+ ---
137
+
138
+ <div align="center">
139
+ Made with ❤️ by <a href="https://github.com/S4NKALP">Sankalp</a>
140
+ </div>
@@ -52,7 +52,7 @@ def run_commit(
52
52
  ] = False,
53
53
  ) -> None:
54
54
  log_file = get_main_log_path()
55
- logger = configure_logger("devgen.cli.commit", log_file)
55
+ logger = configure_logger("devgen.cli.commit", log_file, console=debug)
56
56
  logger.info(f"Log file: {log_file}")
57
57
  logger.info(
58
58
  f"Options: dry_run={dry_run}, push={push}, debug={debug}, force={force_rebuild}"
@@ -5,7 +5,7 @@ from datetime import datetime
5
5
  from pathlib import Path
6
6
  from typing import Dict, List, Optional
7
7
 
8
- from devgen.utils import configure_logger
8
+ from devgen.utils import configure_logger, run_git_command
9
9
 
10
10
 
11
11
  class ChangelogGenerator:
@@ -14,22 +14,6 @@ class ChangelogGenerator:
14
14
  def __init__(self, logger=None):
15
15
  self.logger = logger or configure_logger("devgen.changelog")
16
16
 
17
- def _exec_git(self, args: List[str]) -> str:
18
- """Executes a git command."""
19
- try:
20
- res = subprocess.run(
21
- args,
22
- capture_output=True,
23
- text=True,
24
- encoding="utf-8",
25
- errors="replace",
26
- check=True,
27
- )
28
- return res.stdout.strip()
29
- except subprocess.CalledProcessError as e:
30
- self.logger.error(f"Git command failed: {e}")
31
- raise RuntimeError(f"Git command failed: {e}")
32
-
33
17
  def get_commits(self, from_ref: str = "", to_ref: str = "HEAD") -> List[str]:
34
18
  """Fetches commit messages in the specified range."""
35
19
  range_spec = f"{from_ref}..{to_ref}" if from_ref else to_ref
@@ -40,7 +24,7 @@ class ChangelogGenerator:
40
24
  if not from_ref:
41
25
  # If no start ref, try to find the last tag
42
26
  try:
43
- last_tag = self._exec_git(["git", "describe", "--tags", "--abbrev=0"])
27
+ last_tag = run_git_command(["git", "describe", "--tags", "--abbrev=0"])
44
28
  cmd = [
45
29
  "git",
46
30
  "log",
@@ -49,11 +33,15 @@ class ChangelogGenerator:
49
33
  f"{last_tag}..HEAD",
50
34
  ]
51
35
  self.logger.info(f"Generating changelog from last tag: {last_tag}")
52
- except RuntimeError:
36
+ except (RuntimeError, subprocess.CalledProcessError):
53
37
  self.logger.info("No tags found, generating for all commits.")
54
38
  cmd = ["git", "log", f"--format={fmt}", "--date=short"]
55
39
 
56
- return self._exec_git(cmd).split("\n")
40
+ try:
41
+ return run_git_command(cmd).split("\n")
42
+ except subprocess.CalledProcessError as e:
43
+ self.logger.error(f"Git command failed: {e}")
44
+ raise RuntimeError(f"Git command failed: {e}")
57
45
 
58
46
  def parse_commits(self, raw_commits: List[str]) -> Dict[str, List[Dict]]:
59
47
  """Parses raw commit strings into structured data."""
@@ -11,8 +11,13 @@ from devgen.utils import (
11
11
  get_commit_dry_run_path,
12
12
  is_file_recent,
13
13
  load_template_env,
14
+ run_git_command,
14
15
  sanitize_ai_commit_message,
15
16
  )
17
+ from rich.console import Console
18
+ from rich.panel import Panel
19
+ from rich.markdown import Markdown
20
+ from rich.theme import Theme
16
21
 
17
22
 
18
23
  class CommitEngineError(Exception):
@@ -45,48 +50,44 @@ class CommitEngine:
45
50
  self.provider = provider
46
51
  self.model = model
47
52
  self.logger = logger or configure_logger(
48
- "devgen.commit", Path.home() / ".cache" / "devgen" / "commit.log"
53
+ "devgen.commit",
54
+ Path.home() / ".cache" / "devgen" / "commit.log",
55
+ console=debug,
49
56
  )
50
57
  self.kwargs = kwargs
51
58
  self.dry_run_path = get_commit_dry_run_path()
52
59
  self.template_env = load_template_env("commit")
53
60
 
61
+ self.console = Console(
62
+ theme=Theme(
63
+ {"info": "dim cyan", "warning": "magenta", "danger": "bold red"}
64
+ )
65
+ )
66
+
54
67
  # Load config from ~/.devgen.yaml
55
68
  from devgen.utils import load_config
56
69
 
57
70
  self.config = load_config()
58
71
 
59
- def _exec_git(self, args: List[str], allow_error: bool = False) -> str:
60
- """Executes a git command."""
72
+ def detect_changes(self) -> List[str]:
73
+ """Detects changed, deleted, or untracked files."""
61
74
  try:
62
- res = subprocess.run(
63
- args,
64
- capture_output=True,
65
- text=True,
66
- encoding="utf-8",
67
- errors="replace",
68
- check=not allow_error,
75
+ out = run_git_command(
76
+ [
77
+ "git",
78
+ "ls-files",
79
+ "--deleted",
80
+ "--modified",
81
+ "--others",
82
+ "--exclude-standard",
83
+ ]
69
84
  )
70
- return res.stdout.strip()
85
+ return [f.strip() for f in out.split("\n") if f.strip()]
71
86
  except subprocess.CalledProcessError as e:
72
87
  msg = f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
73
88
  self.logger.error(msg)
74
89
  raise CommitEngineError(msg) from e
75
90
 
76
- def detect_changes(self) -> List[str]:
77
- """Detects changed, deleted, or untracked files."""
78
- out = self._exec_git(
79
- [
80
- "git",
81
- "ls-files",
82
- "--deleted",
83
- "--modified",
84
- "--others",
85
- "--exclude-standard",
86
- ]
87
- )
88
- return [f.strip() for f in out.split("\n") if f.strip()]
89
-
90
91
  def group_files(self, files: List[str]) -> Dict[str, List[str]]:
91
92
  """Groups files by their parent directory."""
92
93
  groups = defaultdict(list)
@@ -98,7 +99,14 @@ class CommitEngine:
98
99
 
99
100
  def generate_diff(self, files: List[str]) -> str:
100
101
  """Generates diff for specific files."""
101
- return self._exec_git(["git", "--no-pager", "diff", "--staged", "--", *files])
102
+ try:
103
+ return run_git_command(
104
+ ["git", "--no-pager", "diff", "--staged", "--", *files]
105
+ )
106
+ except subprocess.CalledProcessError as e:
107
+ msg = f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
108
+ self.logger.error(msg)
109
+ raise CommitEngineError(msg) from e
102
110
 
103
111
  def _init_dry_run(self):
104
112
  """Initializes the dry-run file."""
@@ -108,7 +116,15 @@ class CommitEngine:
108
116
  f.write(f"# Dry Run: Commit Messages\n_Generated: {ts}_\n\n")
109
117
 
110
118
  def _log_dry_run(self, group: str, msg: str):
111
- """Appends a dry-run entry."""
119
+ """Appends a dry-run entry and prints to console."""
120
+ self.console.print(
121
+ Panel(
122
+ Markdown(msg),
123
+ title=f"Dry Run: {group}",
124
+ border_style="yellow",
125
+ expand=False,
126
+ )
127
+ )
112
128
  with self.dry_run_path.open("a", encoding="utf-8") as f:
113
129
  f.write(f"## Group: `{group}`\n\n```md\n{msg}\n```\n\n---\n\n")
114
130
 
@@ -117,18 +133,40 @@ class CommitEngine:
117
133
  if not files:
118
134
  return
119
135
  self.logger.info(f"Staging: {files}")
120
- self._exec_git(["git", "add", *files])
136
+ self.console.print(f"[info]Staging {len(files)} files...[/info]")
137
+ try:
138
+ run_git_command(["git", "add", *files])
139
+ except subprocess.CalledProcessError as e:
140
+ msg = f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
141
+ self.logger.error(msg)
142
+ raise CommitEngineError(msg) from e
121
143
 
122
144
  def commit_staged(self, msg: str):
123
145
  """Commits staged changes."""
124
146
  self.logger.info(f"Committing:\n{msg}")
125
- self._exec_git(["git", "commit", "-m", msg])
147
+ self.console.print(
148
+ Panel(Markdown(msg), title="Commit Message", border_style="green")
149
+ )
150
+ try:
151
+ run_git_command(["git", "commit", "-m", msg])
152
+ except subprocess.CalledProcessError as e:
153
+ msg = f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
154
+ self.logger.error(msg)
155
+ raise CommitEngineError(msg) from e
126
156
 
127
157
  def push_commits(self):
128
158
  """Pushes commits to remote."""
129
159
  self.logger.info("Pushing to remote...")
130
- self._exec_git(["git", "push"])
131
- self.logger.info("Push successful.")
160
+ with self.console.status("[bold green]Pushing to remote...[/bold green]"):
161
+ try:
162
+ run_git_command(["git", "push"])
163
+ except subprocess.CalledProcessError as e:
164
+ msg = (
165
+ f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
166
+ )
167
+ self.logger.error(msg)
168
+ raise CommitEngineError(msg) from e
169
+ self.console.print("[bold green]Push successful.[/bold green]")
132
170
 
133
171
  def generate_message(self, group: str, diff: str, cache: Dict[str, str]) -> str:
134
172
  """Generates a commit message using AI or cache."""
@@ -147,28 +185,32 @@ class CommitEngine:
147
185
  template = self.template_env.get_template("commit_message.j2")
148
186
  prompt = template.render(group_name=group, diff_text=diff, use_emoji=use_emoji)
149
187
 
150
- raw = generate_with_ai(
151
- prompt,
152
- provider=provider,
153
- model=model,
154
- api_key=api_key,
155
- debug=self.debug,
156
- **self.kwargs,
157
- )
188
+ with self.console.status("[bold blue]Generating commit message...[/bold blue]"):
189
+ raw = generate_with_ai(
190
+ prompt,
191
+ provider=provider,
192
+ model=model,
193
+ api_key=api_key,
194
+ debug=self.debug,
195
+ **self.kwargs,
196
+ )
158
197
  return sanitize_ai_commit_message(raw)
159
198
 
160
199
  def is_ahead_of_remote(self) -> bool:
161
200
  """Checks if local branch has unpushed commits."""
162
201
  try:
163
- self._exec_git(["git", "fetch", "origin"])
164
- count = self._exec_git(
165
- ["git", "rev-list", "--count", "@{u}..HEAD"], allow_error=True
202
+ run_git_command(["git", "fetch", "origin"])
203
+ count = run_git_command(
204
+ ["git", "rev-list", "--count", "@{u}..HEAD"], check=False
166
205
  )
167
206
  if count and int(count) > 0:
168
207
  return True
169
- except CommitEngineError:
208
+ except (subprocess.CalledProcessError, CommitEngineError):
170
209
  # Maybe no upstream
171
- return bool(self._exec_git(["git", "rev-parse", "HEAD"], allow_error=True))
210
+ try:
211
+ return bool(run_git_command(["git", "rev-parse", "HEAD"], check=False))
212
+ except subprocess.CalledProcessError:
213
+ return False
172
214
  return False
173
215
 
174
216
  def load_cache(self) -> Dict[str, str]:
@@ -190,7 +232,10 @@ class CommitEngine:
190
232
 
191
233
  if not diff.strip():
192
234
  self.logger.info(f"Skipping empty diff for {group}")
193
- self._exec_git(["git", "reset", "HEAD", "--", *files])
235
+ try:
236
+ run_git_command(["git", "reset", "HEAD", "--", *files])
237
+ except subprocess.CalledProcessError:
238
+ pass # Ignore reset errors
194
239
  return True
195
240
 
196
241
  msg = self.generate_message(group, diff, cache)
@@ -200,7 +245,10 @@ class CommitEngine:
200
245
 
201
246
  if self.dry_run:
202
247
  self._log_dry_run(group, msg)
203
- self._exec_git(["git", "reset", "HEAD", "--", *files])
248
+ try:
249
+ run_git_command(["git", "reset", "HEAD", "--", *files])
250
+ except subprocess.CalledProcessError:
251
+ pass
204
252
  else:
205
253
  self.commit_staged(msg)
206
254
 
@@ -237,16 +285,19 @@ class CommitEngine:
237
285
  self.logger.error("Push aborted due to failed commits.")
238
286
 
239
287
  if self.dry_run:
240
- self.logger.info(f"Dry run done. See {self.dry_run_path}")
288
+ self.console.print(
289
+ f"[bold green]Dry run done.[/bold green] See {self.dry_run_path}"
290
+ )
241
291
  else:
242
- self.logger.info("Done.")
292
+ self.console.print("[bold green]Done.[/bold green]")
243
293
  if failed:
244
- self.logger.warning(f"Failed groups: {failed}")
294
+ self.console.print(f"[bold red]Failed groups: {failed}[/bold red]")
245
295
 
246
296
 
247
297
  def run_commit_engine(**kwargs):
248
298
  """Entry point for the commit engine."""
249
- logger = configure_logger("devgen.commit")
299
+ debug = kwargs.get("debug", False)
300
+ logger = configure_logger("devgen.commit", console=debug)
250
301
  try:
251
302
  engine = CommitEngine(**kwargs)
252
303
  engine.execute()
@@ -1,21 +1,24 @@
1
- from devgen.providers.anthropic import AnthropicProvider
2
- from devgen.providers.gemini import GeminiProvider
3
- from devgen.providers.huggingface import HuggingfaceProvider
4
- from devgen.providers.openai import OpenaiProvider
5
- from devgen.providers.openrouter import OpenrouterProvider
6
-
7
-
8
1
  def get_provider(name):
9
2
  name_lower = name.lower()
10
3
  if name_lower == "gemini":
4
+ from devgen.providers.gemini import GeminiProvider
5
+
11
6
  return GeminiProvider()
12
7
  elif name_lower == "openai":
8
+ from devgen.providers.openai import OpenaiProvider
9
+
13
10
  return OpenaiProvider()
14
11
  elif name_lower == "huggingface":
12
+ from devgen.providers.huggingface import HuggingfaceProvider
13
+
15
14
  return HuggingfaceProvider()
16
15
  elif name_lower == "openrouter":
16
+ from devgen.providers.openrouter import OpenrouterProvider
17
+
17
18
  return OpenrouterProvider()
18
19
  elif name_lower == "anthropic":
20
+ from devgen.providers.anthropic import AnthropicProvider
21
+
19
22
  return AnthropicProvider()
20
23
 
21
24
  raise NotImplementedError(f"Provider '{name}' is not implemented yet.")