gitdude 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,305 @@
1
+ Metadata-Version: 2.4
2
+ Name: gitdude
3
+ Version: 1.0.0
4
+ Summary: AI-powered Git workflow assistant — commit, review, recover, and more with a single command
5
+ Project-URL: Homepage, https://github.com/utkarshgupta188/gitdude
6
+ Project-URL: Repository, https://github.com/utkarshgupta188/gitdude
7
+ Project-URL: Documentation, https://github.com/utkarshgupta188/gitdude#readme
8
+ Project-URL: Bug Tracker, https://github.com/utkarshgupta188/gitdude/issues
9
+ Author: GitDude Contributors
10
+ License: MIT
11
+ Keywords: ai,cli,developer-tools,gemini,git,groq,ollama,openai
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Version Control :: Git
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: gitpython>=3.1.40
26
+ Requires-Dist: google-generativeai>=0.7.0
27
+ Requires-Dist: groq>=0.9.0
28
+ Requires-Dist: ollama>=0.2.0
29
+ Requires-Dist: openai>=1.30.0
30
+ Requires-Dist: pyperclip>=1.8.2
31
+ Requires-Dist: rich>=13.7.0
32
+ Requires-Dist: typer[all]>=0.12.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
35
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
36
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
37
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # GitDude 🧠
41
+
42
+ [![PyPI version](https://img.shields.io/pypi/v/gitwise.svg)](https://pypi.org/project/gitwise/)
43
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
45
+ [![Downloads](https://img.shields.io/pypi/dm/gitwise)](https://pypi.org/project/gitwise/)
46
+
47
+ **GitDude is an AI-powered Git workflow assistant that turns plain English into git actions — commit, review, recover, and understand your repo from the terminal.**
48
+
49
+ ---
50
+
51
+ ## ✨ Features
52
+
53
+ - 🤖 **Multi-provider AI** — Gemini (default, free), Groq (fastest), Ollama (100% local), OpenAI
54
+ - 💬 **Natural language commands** — `gitwise do "stash, switch to main, pull"`
55
+ - 🔍 **AI code review** — bugs, security issues, quality flags before you push
56
+ - 🚑 **Emergency recovery** — `gitwise whoops` diagnoses and fixes git disasters
57
+ - 📋 **PR generation** — full GitHub PR description, auto-copied to clipboard
58
+ - 🎨 **Beautiful Rich UI** — tables, panels, spinners, color-coded output
59
+ - 🔐 **Secure config** — keys stored in `~/.gitdude/config.json`, never in `.env`
60
+
61
+ ---
62
+
63
+ ## 🚀 Quick Start
64
+
65
+ ```bash
66
+ # 1. Install
67
+ pip install gitdude
68
+
69
+ # 2. Configure (takes 60 seconds)
70
+ gitwise config
71
+
72
+ # 3. Generate an AI commit message and push
73
+ gitwise push
74
+
75
+ # 4. Review your code before merging
76
+ gitwise review
77
+
78
+ # 5. Something went wrong? Ask for help
79
+ gitwise whoops
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 📦 Installation
85
+
86
+ ```bash
87
+ pip install gitdude
88
+ ```
89
+
90
+ **Python 3.10+ required.**
91
+
92
+ ---
93
+
94
+ ## 🔑 Provider Setup
95
+
96
+ ### Google Gemini (Default — Free)
97
+
98
+ 1. Get an API key from [Google AI Studio](https://aistudio.google.com/app/apikey)
99
+ 2. Run `gitwise config` → choose `gemini` → paste key
100
+
101
+ ### Groq (Fastest Free Tier)
102
+
103
+ 1. Get a key at [console.groq.com/keys](https://console.groq.com/keys)
104
+ 2. Run `gitwise config` → choose `groq` → paste key
105
+
106
+ ### Ollama (100% Local/Offline)
107
+
108
+ 1. Install Ollama: [ollama.ai](https://ollama.ai)
109
+ 2. Pull a model: `ollama pull llama3`
110
+ 3. Run `gitwise config` → choose `ollama` (no key needed)
111
+
112
+ ### OpenAI
113
+
114
+ 1. Get a key at [platform.openai.com](https://platform.openai.com/api-keys)
115
+ 2. Run `gitwise config` → choose `openai` → paste key
116
+
117
+ ---
118
+
119
+ ## 📖 Command Reference
120
+
121
+ ### `gitwise push`
122
+
123
+ AI-generates a commit message for your changes, lets you confirm/edit, then commits and pushes.
124
+
125
+ ```bash
126
+ gitwise push # Stage all → AI commit → push
127
+ gitwise push --no-confirm # Skip confirmation
128
+ gitwise push --dry-run # Preview only, don't execute
129
+ gitwise push --style freeform # Use freeform (not conventional) commit style
130
+ ```
131
+
132
+ **Commit types (conventional):** `feat`, `fix`, `chore`, `docs`, `refactor`, `style`, `test`, `perf`, `ci`
133
+
134
+ ---
135
+
136
+ ### `gitwise sync`
137
+
138
+ Fetch + rebase. If there are merge conflicts, AI explains what's conflicting and gives exact resolution steps.
139
+
140
+ ```bash
141
+ gitwise sync
142
+ gitwise sync --dry-run
143
+ ```
144
+
145
+ ---
146
+
147
+ ### `gitwise back`
148
+
149
+ Shows last 30 commits in a table. Pick one to go back to, choose a mode.
150
+
151
+ ```bash
152
+ gitwise back
153
+ gitwise back --dry-run
154
+ ```
155
+
156
+ **Modes:** `soft` (keep changes staged), `hard` (discard changes), `checkout` (detached HEAD), `branch` (new branch from that commit)
157
+
158
+ ---
159
+
160
+ ### `gitwise undo "<description>"`
161
+
162
+ Describe what went wrong — AI reads your log and reflog to determine the safest recovery.
163
+
164
+ ```bash
165
+ gitwise undo "I accidentally committed my .env file"
166
+ gitwise undo "I deleted the wrong branch"
167
+ gitwise undo "I made a mess of the last 3 commits" --dry-run
168
+ ```
169
+
170
+ ---
171
+
172
+ ### `gitwise do "<natural language>"`
173
+
174
+ Convert plain English into a sequence of git commands, preview, then execute.
175
+
176
+ ```bash
177
+ gitwise do "stash my changes, switch to main, pull latest"
178
+ gitwise do "create a new branch called feature/auth and push it"
179
+ gitwise do "squash my last 3 commits" --dry-run
180
+ ```
181
+
182
+ If any command fails, AI reads the error and suggests a fix.
183
+
184
+ ---
185
+
186
+ ### `gitwise review`
187
+
188
+ Diffs your current branch vs main/master, then AI reviews for bugs, security issues, and quality.
189
+
190
+ ```bash
191
+ gitwise review
192
+ gitwise review --base develop # Compare against a different branch
193
+ gitwise review --dry-run # Show diff without AI review
194
+ ```
195
+
196
+ **Review sections:** Summary · ⚠️ Potential Bugs · 🔒 Security Issues · 📏 Code Quality · 🔍 Things to Check · ✅ Overall Assessment
197
+
198
+ ---
199
+
200
+ ### `gitwise pr`
201
+
202
+ Generates a complete PR title + description + bullet list + testing notes. Auto-copies to clipboard.
203
+
204
+ ```bash
205
+ gitwise pr
206
+ gitwise pr --base develop # Compare against develop
207
+ gitwise pr --no-copy # Don't copy to clipboard
208
+ ```
209
+
210
+ ---
211
+
212
+ ### `gitwise explain`
213
+
214
+ Reads your last 5 reflog entries and explains in plain English what happened.
215
+
216
+ ```bash
217
+ gitwise explain
218
+ ```
219
+
220
+ ---
221
+
222
+ ### `gitwise whoops`
223
+
224
+ Emergency recovery. Feeds git status + log + reflog to AI. Diagnoses what went wrong and gives step-by-step recovery.
225
+
226
+ ```bash
227
+ gitwise whoops
228
+ gitwise whoops --dry-run # Diagnose only, don't execute
229
+ ```
230
+
231
+ ---
232
+
233
+ ### `gitwise config`
234
+
235
+ Interactive configuration wizard.
236
+
237
+ ```bash
238
+ gitwise config # Run setup
239
+ gitwise config --show # Print current config (keys masked)
240
+ gitwise config --reset # Wipe config and redo setup
241
+ ```
242
+
243
+ **Stored settings:**
244
+ - AI provider (`gemini` / `groq` / `ollama` / `openai`)
245
+ - API key per provider
246
+ - Model name per provider
247
+ - Default branch (`main` or `master`)
248
+ - Commit style (`conventional` or `freeform`)
249
+
250
+ ---
251
+
252
+ ## 🏗️ Architecture
253
+
254
+ ```
255
+ gitwise/
256
+ ├── main.py # Typer app — all command definitions
257
+ ├── ai.py # Unified AI provider wrapper (ask_ai)
258
+ ├── git_ops.py # GitPython operations (diff, log, push, reset…)
259
+ ├── config.py # Config at ~/.gitdude/config.json
260
+ └── utils.py # Rich panels, tables, prompts, helpers
261
+ ```
262
+
263
+ **AI Providers (`ai.py`):**
264
+
265
+ | Provider | SDK | Default Model | Speed | Cost |
266
+ |---|---|---|---|---|
267
+ | `gemini` | `google-generativeai` | `gemini-2.0-flash` | Fast | Free tier |
268
+ | `groq` | `groq` | `llama-3.3-70b-versatile` | Fastest | Free tier |
269
+ | `ollama` | `ollama` | `llama3` | Local | Free (local) |
270
+ | `openai` | `openai` | `gpt-4o-mini` | Fast | Pay per use |
271
+
272
+ All providers go through a single interface: `ask_ai(prompt) -> str` with spinner feedback and unified error handling.
273
+
274
+ ---
275
+
276
+ ## 🛡️ Safety Features
277
+
278
+ - ✅ **All destructive operations** (hard reset, force push, etc.) require explicit confirmation
279
+ - ✅ **`--dry-run` flag** available on all mutating commands
280
+ - ✅ **Color-coded risk levels** — green (safe), yellow (caution), red (destructive)
281
+ - ✅ **No tracebacks** — all exceptions caught and shown as friendly Rich error panels
282
+ - ✅ **API keys** stored privately in `~/.gitdude/config.json`, never in project files
283
+
284
+ ---
285
+
286
+ ## 🤝 Contributing
287
+
288
+ 1. Fork the repo
289
+ 2. Create a feature branch: `git checkout -b feat/amazing-feature`
290
+ 3. Make your changes
291
+ 4. Run linting: `ruff check .`
292
+ 5. Open a PR — or just use `gitwise pr` to generate your PR description! 😄
293
+
294
+ **Dev setup:**
295
+ ```bash
296
+ git clone https://github.com/gitwise/gitwise
297
+ cd gitwise
298
+ pip install -e ".[dev]"
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 📄 License
304
+
305
+ MIT © GitWise Contributors
@@ -0,0 +1,10 @@
1
+ gitwise/__init__.py,sha256=mXFDA4plrZAZBHC1UQTMQRovGMNs4fHgCVNP-nwoSGM,132
2
+ gitwise/ai.py,sha256=fIcRAzlFxy4IIT6oiBOuRNie7B0STD5iPil00tJ_bKM,4757
3
+ gitwise/config.py,sha256=MfPlqAJ2pAwF4fNHl7imFYY-wXDj4Bxec-z_4MZKo1Q,2902
4
+ gitwise/git_ops.py,sha256=BIvF7l-OU8MwxKP7DqYA2DzTsDpQypQ5SLujwKR6sW0,6759
5
+ gitwise/main.py,sha256=xylYXLW_xP6Ui-YIM1_7d2TzwJFvizVGuhaPeRJw52s,31504
6
+ gitwise/utils.py,sha256=VV4RJ9Uqbf-XWbtwSIk_DGLO_0NMhQ80X9pFLB-Blgc,5154
7
+ gitdude-1.0.0.dist-info/METADATA,sha256=jL8pf71h4IZMIaXEQbTcYrygZAaPtBhPpBKf7D0I0Os,8874
8
+ gitdude-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
9
+ gitdude-1.0.0.dist-info/entry_points.txt,sha256=AWdMzCVsC0k4pMbdRnbB5aYpNsrkoiwi3rLuYis36JM,46
10
+ gitdude-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gitwise = gitwise.main:main
gitwise/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """GitDude — AI-powered Git workflow assistant."""
2
+
3
+ __version__ = "1.0.0"
4
+ __author__ = "GitDude Contributors"
5
+ __license__ = "MIT"
gitwise/ai.py ADDED
@@ -0,0 +1,148 @@
1
+ """
2
+ ai.py — Unified AI provider wrapper.
3
+
4
+ Single public function: ask_ai(prompt: str) -> str
5
+ Supports: gemini, groq, ollama, openai
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ from typing import Optional
12
+
13
+ from rich.console import Console
14
+ from rich.live import Live
15
+ from rich.spinner import Spinner
16
+
17
+ from gitwise.config import (
18
+ get_config,
19
+ get_current_provider,
20
+ get_model_for_provider,
21
+ get_provider_api_key,
22
+ is_configured,
23
+ )
24
+
25
+ console = Console()
26
+
27
+
28
+ def _spinner_context(message: str = "🤖 Thinking..."):
29
+ """Return a Rich Live context showing a spinner."""
30
+ return Live(Spinner("dots", text=f"[bold magenta]{message}[/bold magenta]"), console=console, transient=True)
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Provider implementations
35
+ # ---------------------------------------------------------------------------
36
+
37
+ def _ask_gemini(prompt: str, model: str, api_key: str) -> str:
38
+ try:
39
+ import google.generativeai as genai # type: ignore
40
+ except ImportError:
41
+ raise RuntimeError(
42
+ "google-generativeai is not installed. Run: pip install google-generativeai"
43
+ )
44
+ genai.configure(api_key=api_key)
45
+ client = genai.GenerativeModel(model)
46
+ response = client.generate_content(prompt)
47
+ return response.text.strip()
48
+
49
+
50
+ def _ask_groq(prompt: str, model: str, api_key: str) -> str:
51
+ try:
52
+ from groq import Groq # type: ignore
53
+ except ImportError:
54
+ raise RuntimeError("groq is not installed. Run: pip install groq")
55
+ client = Groq(api_key=api_key)
56
+ chat_completion = client.chat.completions.create(
57
+ messages=[{"role": "user", "content": prompt}],
58
+ model=model,
59
+ )
60
+ return chat_completion.choices[0].message.content.strip()
61
+
62
+
63
+ def _ask_ollama(prompt: str, model: str, **_kwargs) -> str:
64
+ try:
65
+ import ollama # type: ignore
66
+ except ImportError:
67
+ raise RuntimeError("ollama is not installed. Run: pip install ollama")
68
+ response = ollama.chat(
69
+ model=model,
70
+ messages=[{"role": "user", "content": prompt}],
71
+ )
72
+ return response["message"]["content"].strip()
73
+
74
+
75
+ def _ask_openai(prompt: str, model: str, api_key: str) -> str:
76
+ try:
77
+ from openai import OpenAI # type: ignore
78
+ except ImportError:
79
+ raise RuntimeError("openai is not installed. Run: pip install openai")
80
+ client = OpenAI(api_key=api_key)
81
+ completion = client.chat.completions.create(
82
+ model=model,
83
+ messages=[{"role": "user", "content": prompt}],
84
+ )
85
+ return completion.choices[0].message.content.strip()
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Public interface
90
+ # ---------------------------------------------------------------------------
91
+
92
+ def ask_ai(prompt: str, spinner_msg: str = "🤖 Thinking...") -> str:
93
+ """
94
+ Send a prompt to the configured AI provider and return the response.
95
+ Shows a Rich spinner while waiting.
96
+ Raises SystemExit on unrecoverable errors.
97
+ """
98
+ from gitwise.utils import error_panel # late import to avoid circular
99
+
100
+ if not is_configured():
101
+ error_panel(
102
+ "GitWise is not configured yet.\nRun [bold cyan]gitwise config[/bold cyan] to get started.",
103
+ title="❌ Not Configured",
104
+ )
105
+ sys.exit(1)
106
+
107
+ cfg = get_config()
108
+ provider = get_current_provider()
109
+ model = get_model_for_provider(provider)
110
+ api_key = get_provider_api_key(provider)
111
+
112
+ if provider != "ollama" and not api_key:
113
+ error_panel(
114
+ f"No API key found for provider [bold]{provider}[/bold].\n"
115
+ f"Run [bold cyan]gitwise config[/bold cyan] to set your key.",
116
+ title="❌ Missing API Key",
117
+ )
118
+ sys.exit(1)
119
+
120
+ provider_fn = {
121
+ "gemini": _ask_gemini,
122
+ "groq": _ask_groq,
123
+ "ollama": _ask_ollama,
124
+ "openai": _ask_openai,
125
+ }.get(provider)
126
+
127
+ if provider_fn is None:
128
+ error_panel(
129
+ f"Unknown provider: [bold]{provider}[/bold]\n"
130
+ f"Run [bold cyan]gitwise config[/bold cyan] to choose a valid provider.",
131
+ title="❌ Invalid Provider",
132
+ )
133
+ sys.exit(1)
134
+
135
+ try:
136
+ with _spinner_context(spinner_msg):
137
+ result = provider_fn(prompt=prompt, model=model, api_key=api_key)
138
+ return result
139
+ except RuntimeError as exc:
140
+ error_panel(str(exc), title="❌ AI Provider Error")
141
+ sys.exit(1)
142
+ except Exception as exc: # noqa: BLE001
143
+ error_panel(
144
+ f"An error occurred while calling [bold]{provider}[/bold]:\n{exc}\n\n"
145
+ "Check your API key, network connection, and model name.",
146
+ title="❌ AI Call Failed",
147
+ )
148
+ sys.exit(1)
gitwise/config.py ADDED
@@ -0,0 +1,106 @@
1
+ """
2
+ config.py — GitWise configuration management.
3
+ Stores config in ~/.gitwise/config.json.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ CONFIG_DIR = Path.home() / ".gitwise"
14
+ CONFIG_FILE = CONFIG_DIR / "config.json"
15
+
16
+ PROVIDERS = ["gemini", "groq", "ollama", "openai"]
17
+
18
+ DEFAULTS: dict[str, Any] = {
19
+ "provider": "gemini",
20
+ "model": {
21
+ "gemini": "gemini-2.0-flash",
22
+ "groq": "llama-3.3-70b-versatile",
23
+ "ollama": "llama3",
24
+ "openai": "gpt-4o-mini",
25
+ },
26
+ "api_key": {
27
+ "gemini": "",
28
+ "groq": "",
29
+ "ollama": "",
30
+ "openai": "",
31
+ },
32
+ "default_branch": "main",
33
+ "commit_style": "conventional",
34
+ }
35
+
36
+
37
+ def load_config() -> dict[str, Any]:
38
+ """Load config from disk, returning defaults if missing."""
39
+ if not CONFIG_FILE.exists():
40
+ return {}
41
+ try:
42
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
43
+ return json.load(f)
44
+ except (json.JSONDecodeError, OSError):
45
+ return {}
46
+
47
+
48
+ def save_config(cfg: dict[str, Any]) -> None:
49
+ """Persist config to disk."""
50
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
51
+ with open(CONFIG_FILE, "w", encoding="utf-8") as f:
52
+ json.dump(cfg, f, indent=2)
53
+
54
+
55
+ def get_config() -> dict[str, Any]:
56
+ """Return merged config with defaults."""
57
+ cfg = load_config()
58
+ # Deep merge with defaults
59
+ merged = {
60
+ "provider": cfg.get("provider", DEFAULTS["provider"]),
61
+ "model": {**DEFAULTS["model"], **cfg.get("model", {})},
62
+ "api_key": {**DEFAULTS["api_key"], **cfg.get("api_key", {})},
63
+ "default_branch": cfg.get("default_branch", DEFAULTS["default_branch"]),
64
+ "commit_style": cfg.get("commit_style", DEFAULTS["commit_style"]),
65
+ }
66
+ return merged
67
+
68
+
69
+ def is_configured() -> bool:
70
+ """Return True if a config file exists with at least a provider set."""
71
+ return CONFIG_FILE.exists()
72
+
73
+
74
+ def get_provider_api_key(provider: str) -> str:
75
+ """Get API key for the given provider from config."""
76
+ cfg = get_config()
77
+ # Also allow environment variable overrides
78
+ env_map = {
79
+ "gemini": "GEMINI_API_KEY",
80
+ "groq": "GROQ_API_KEY",
81
+ "openai": "OPENAI_API_KEY",
82
+ "ollama": "",
83
+ }
84
+ env_key = env_map.get(provider, "")
85
+ if env_key:
86
+ env_val = os.environ.get(env_key, "")
87
+ if env_val:
88
+ return env_val
89
+ return cfg.get("api_key", {}).get(provider, "")
90
+
91
+
92
+ def get_current_provider() -> str:
93
+ return get_config().get("provider", "gemini")
94
+
95
+
96
+ def get_model_for_provider(provider: str) -> str:
97
+ return get_config().get("model", {}).get(provider, DEFAULTS["model"].get(provider, ""))
98
+
99
+
100
+ def mask_key(key: str) -> str:
101
+ """Mask an API key for display."""
102
+ if not key:
103
+ return "<not set>"
104
+ if len(key) <= 8:
105
+ return "****"
106
+ return key[:4] + "****" + key[-4:]