git-copilot-commit 0.1.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.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2025 Dheepak Krishnamurthy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: git-copilot-commit
3
+ Version: 0.1.0
4
+ Summary: Automatically generate and commit changes using copilot
5
+ Author-email: Dheepak Krishnamurthy <1813121+kdheepak@users.noreply.github.com>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: pycopilot>=0.1.1
9
+ Requires-Dist: rich>=14.0.0
10
+ Requires-Dist: typer>=0.16.0
11
+ Requires-Dist: xdg-base-dirs>=6.0.2
File without changes
@@ -0,0 +1,22 @@
1
+ [project]
2
+ name = "git-copilot-commit"
3
+ version = "0.1.0"
4
+ description = "Automatically generate and commit changes using copilot"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Dheepak Krishnamurthy", email = "1813121+kdheepak@users.noreply.github.com" },
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "pycopilot>=0.1.1",
12
+ "rich>=14.0.0",
13
+ "typer>=0.16.0",
14
+ "xdg-base-dirs>=6.0.2",
15
+ ]
16
+
17
+ [project.scripts]
18
+ git-copilot-commit = "git_copilot_commit.cli:app"
19
+
20
+ [build-system]
21
+ requires = ["hatchling"]
22
+ build-backend = "hatchling.build"
@@ -0,0 +1,369 @@
1
+ """
2
+ git-copilot-commit - AI-powered Git commit assistant
3
+ """
4
+
5
+ import typer
6
+ from rich.console import Console
7
+ from rich.prompt import Confirm
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+
11
+ from pycopilot.copilot import Copilot
12
+ from pycopilot.auth import Authentication
13
+ from .git import GitRepository, GitError, NotAGitRepositoryError, GitStatus
14
+
15
+ console = Console()
16
+ app = typer.Typer(help=__doc__, add_completion=False)
17
+
18
+
19
+ @app.callback(invoke_without_command=True)
20
+ def main(ctx: typer.Context):
21
+ """
22
+ Automatically commit changes in the current git repository.
23
+ """
24
+ if ctx.invoked_subcommand is None:
25
+ # Show help when no command is provided
26
+ print(ctx.get_help())
27
+ raise typer.Exit()
28
+
29
+
30
+ def display_file_status(status: GitStatus) -> None:
31
+ """Display file status in a rich table."""
32
+ if not status.files:
33
+ return
34
+
35
+ table = Table(title="Git Status")
36
+ table.add_column("Status", style="yellow", width=8)
37
+ table.add_column("File", style="white")
38
+
39
+ # Group files by status
40
+ staged = status.staged_files
41
+ unstaged = status.unstaged_files
42
+ untracked = status.untracked_files
43
+
44
+ if staged:
45
+ table.add_row("[green]Staged[/green]", "", style="dim")
46
+ for file in staged:
47
+ table.add_row(f" {file.staged_status}", file.path)
48
+
49
+ if unstaged:
50
+ table.add_row("[yellow]Unstaged[/yellow]", "", style="dim")
51
+ for file in unstaged:
52
+ table.add_row(f" {file.status}", file.path)
53
+
54
+ if untracked:
55
+ table.add_row("[red]Untracked[/red]", "", style="dim")
56
+ for file in untracked:
57
+ table.add_row(" ?", file.path)
58
+
59
+ console.print(table)
60
+
61
+
62
+ def generate_commit_message(repo: GitRepository, status: GitStatus) -> str:
63
+ """Generate a conventional commit message using Copilot API."""
64
+
65
+ # Get recent commits for context
66
+ recent_commits = repo.get_recent_commits(limit=5)
67
+ recent_commits_text = "\n".join([f"- {msg}" for _, msg in recent_commits])
68
+
69
+ client = Copilot(
70
+ system_prompt="""
71
+
72
+ You are a Git commit message assistant trained to write a single clear, structured, and informative commit message following the Conventional Commits specification based on the provided `git diff --staged` output.
73
+
74
+ Output format: Provide only the commit message without any additional text, explanations, or formatting markers.
75
+
76
+ The guidelines for the commit messages are as follows:
77
+
78
+ 1. Format
79
+
80
+ ```
81
+ <type>[optional scope]: <description>
82
+ ```
83
+
84
+ - The first line (title) should be at most 72 characters long.
85
+ - If the natural description exceeds 72 characters, prioritize the most important aspect.
86
+ - Use abbreviations when appropriate: `config` not `configuration`.
87
+ - The body (if present) should be wrapped at 100 characters per line.
88
+
89
+ 2. Valid Commit Types:
90
+
91
+ - `feat`: A new feature
92
+ - `fix`: A bug fix
93
+ - `docs`: Documentation changes
94
+ - `style`: Code formatting (no logic changes)
95
+ - `refactor`: Code restructuring (no behavior changes)
96
+ - `perf`: Performance improvements
97
+ - `test`: Adding or updating tests
98
+ - `chore`: Maintenance tasks (e.g., tooling, CI/CD, dependencies)
99
+ - `revert`: Reverting previous changes
100
+
101
+ 3. Scope (Optional but encouraged):
102
+
103
+ - Enclose in parentheses, e.g., feat(auth): add login endpoint.
104
+ - Use the affected module, component, or area: `auth`, `api`, `ui`, `database`, `config`.
105
+ - For multiple files in same area, use the broader scope: `feat(auth): add login and logout endpoints`.
106
+ - For single files, you may use filename: `fix(user-service): handle null email validation`.
107
+ - Scope should be a single word or hyphenated phrase describing the affected module.
108
+
109
+ 4. Description:
110
+
111
+ - Use imperative mood (e.g., "add feature" instead of "added" or "adds").
112
+ - Be concise yet informative.
113
+ - Focus on the primary change, not all details.
114
+ - Do not make assumptions about why the change was made or how it works.
115
+ - Do not say "improving code readability" or similar; focus on just the change itself.
116
+
117
+ 5. Analyzing Git Diffs:
118
+
119
+ - Focus on the logical change, not individual line modifications.
120
+ - Group related file changes under one logical scope.
121
+ - Identify the primary purpose of the change set.
122
+ - If changes span multiple unrelated areas, focus on the most significant one.
123
+
124
+ Examples:
125
+
126
+ ✅ Good Commit Messages:
127
+
128
+ - feat(api): add user authentication with JWT
129
+ - fix(database): handle connection retries properly
130
+ - docs(readme): update installation instructions
131
+ - refactor(utils): simplify date parsing logic
132
+ - chore(deps): update dependencies to latest versions
133
+ - feat(auth): implement OAuth2 integration
134
+ - fix(payments): resolve double-charging bug in subscription renewal
135
+ - refactor(database): extract query builder into separate module
136
+ - chore(ci): add automated security scanning to pipeline
137
+ - docs(api): add OpenAPI specifications for user endpoints
138
+
139
+ ❌ Strongly Avoid:
140
+
141
+ - Vague descriptions: "fixed bug", "updated code", "made changes"
142
+ - Past tense: "added feature", "fixed issue"
143
+ - Explanations of why: "to improve performance", "because users requested"
144
+ - Implementation details: "using React hooks", "with try-catch blocks"
145
+ - Not in imperative mood: "new feature", "updates stuff"
146
+
147
+ Given a Git diff, a list of modified files, or a short description of changes,
148
+ generate a single clear and structured Conventional Commit message following the above rules.
149
+ If multiple changes are detected, prioritize the most important changes in a single commit message.
150
+ Do not add any body or footer.
151
+ You can only give one reply for each conversation turn.
152
+
153
+ Avoid wrapping the whole response in triple backticks.
154
+ """
155
+ )
156
+
157
+ prompt = f"""Recent commits:
158
+ {recent_commits_text}
159
+
160
+ `git status`:
161
+ ```
162
+ {status.get_porcelain_output()}
163
+ ```
164
+
165
+ `git diff --staged`:
166
+ ```
167
+ {status.staged_diff}
168
+ ```
169
+
170
+ Generate a conventional commit message:"""
171
+
172
+ response = client.ask(prompt)
173
+ return response.content
174
+
175
+
176
+ @app.command()
177
+ def commit(
178
+ all_files: bool = typer.Option(
179
+ False, "--all", "-a", help="Stage all files before committing"
180
+ ),
181
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Show verbose output"),
182
+ ):
183
+ """
184
+ Automatically commit changes in the current git repository.
185
+ """
186
+ try:
187
+ repo = GitRepository()
188
+ except NotAGitRepositoryError:
189
+ console.print("[red]Error: Not in a git repository[/red]")
190
+ raise typer.Exit(1)
191
+
192
+ # Get initial status
193
+ status = repo.get_status()
194
+
195
+ if not status.files:
196
+ console.print("[yellow]No changes to commit.[/yellow]")
197
+ raise typer.Exit()
198
+
199
+ # Display current status
200
+ if verbose:
201
+ display_file_status(status)
202
+
203
+ # Handle staging based on options
204
+ if all_files:
205
+ repo.stage_files() # Stage all files
206
+ console.print("[green]Staged all files.[/green]")
207
+ else:
208
+ # Check if we need to stage files
209
+ if status.has_unstaged_changes:
210
+ if Confirm.ask("Stage modified files?"):
211
+ repo.stage_modified()
212
+ console.print("[green]Staged modified files.[/green]")
213
+ else:
214
+ raise typer.Exit()
215
+ if status.has_untracked_files:
216
+ if Confirm.ask("Stage untracked files?"):
217
+ repo.stage_files()
218
+ console.print("[green]Staged untracked files.[/green]")
219
+ else:
220
+ raise typer.Exit()
221
+
222
+ # Refresh status after staging
223
+ status = repo.get_status()
224
+
225
+ if not status.has_staged_changes:
226
+ console.print("[yellow]No staged changes to commit.[/yellow]")
227
+ raise typer.Exit()
228
+
229
+ # Generate or use provided commit message
230
+ console.print("[cyan]Generating commit message...[/cyan]")
231
+ with console.status("[cyan]Generating commit message using Copilot API...[/cyan]"):
232
+ commit_message = generate_commit_message(repo, status)
233
+
234
+ # Display commit message
235
+ console.print(Panel(commit_message, title="Commit Message", border_style="green"))
236
+
237
+ # Show what will be committed in verbose mode
238
+ if verbose:
239
+ console.print("\n[bold]Changes to be committed:[/bold]")
240
+ for file in status.staged_files:
241
+ console.print(f" {file.staged_status} {file.path}")
242
+
243
+ # Confirm commit or edit message
244
+ choice = typer.prompt(
245
+ "Choose action: (c)ommit, (e)dit message, (q)uit",
246
+ default="c",
247
+ show_default=True,
248
+ ).lower()
249
+
250
+ if choice == "q":
251
+ console.print("Commit cancelled.")
252
+ raise typer.Exit()
253
+ elif choice == "e":
254
+ # Use git's built-in editor with generated message as template
255
+ console.print("[cyan]Opening git editor...[/cyan]")
256
+ try:
257
+ commit_sha = repo.commit(commit_message, use_editor=True)
258
+ except GitError as e:
259
+ console.print(f"[red]Commit failed: {e}[/red]")
260
+ raise typer.Exit(1)
261
+ elif choice == "c":
262
+ # Commit with generated message
263
+ try:
264
+ commit_sha = repo.commit(commit_message)
265
+ except GitError as e:
266
+ console.print(f"[red]Commit failed: {e}[/red]")
267
+ raise typer.Exit(1)
268
+ else:
269
+ console.print("Invalid choice. Commit cancelled.")
270
+ raise typer.Exit()
271
+
272
+ # Show success message
273
+ console.print(f"[green]✓ Successfully committed: {commit_sha[:8]}[/green]")
274
+
275
+
276
+ def handle_interactive_staging(repo: GitRepository, status: GitStatus) -> None:
277
+ """Handle interactive file staging."""
278
+ # Combine all files that can be staged
279
+ stageable_files = [f for f in status.files if not f.is_staged]
280
+
281
+ if not stageable_files:
282
+ console.print("[yellow]No files to stage.[/yellow]")
283
+ return
284
+
285
+ table = Table(title="Select files to stage")
286
+ table.add_column("Index", style="cyan", width=6)
287
+ table.add_column("Status", style="yellow", width=8)
288
+ table.add_column("File", style="white")
289
+
290
+ for i, file in enumerate(stageable_files, 1):
291
+ status_char = file.status if not file.is_untracked else "?"
292
+ table.add_row(str(i), status_char, file.path)
293
+
294
+ console.print(table)
295
+
296
+ selection = (
297
+ console.input(
298
+ "\nEnter file numbers to stage (comma-separated), 'all', or 'quit': "
299
+ )
300
+ .strip()
301
+ .lower()
302
+ )
303
+
304
+ if selection == "quit":
305
+ return
306
+ elif selection == "all":
307
+ repo.stage_files([f.path for f in stageable_files])
308
+ console.print(f"[green]Staged {len(stageable_files)} files.[/green]")
309
+ else:
310
+ try:
311
+ indices = [int(x.strip()) - 1 for x in selection.split(",")]
312
+ files_to_stage = [
313
+ stageable_files[i].path
314
+ for i in indices
315
+ if 0 <= i < len(stageable_files)
316
+ ]
317
+
318
+ if files_to_stage:
319
+ repo.stage_files(files_to_stage)
320
+ console.print(f"[green]Staged {len(files_to_stage)} files.[/green]")
321
+ else:
322
+ console.print("[yellow]No valid files selected.[/yellow]")
323
+ except (ValueError, IndexError):
324
+ console.print("[red]Invalid selection.[/red]")
325
+
326
+
327
+ @app.command()
328
+ def authenticate():
329
+ """Autheticate with GitHub Copilot."""
330
+ Authentication().auth()
331
+
332
+
333
+ @app.command()
334
+ def models():
335
+ """List models available for chat in a Rich table."""
336
+ models = Copilot().models
337
+
338
+ console = Console()
339
+ table = Table(title="Available Models")
340
+
341
+ table.add_column("ID", style="cyan", no_wrap=True)
342
+ table.add_column("Name", style="magenta")
343
+ table.add_column("Vendor", style="green")
344
+ table.add_column("Version", style="yellow")
345
+ table.add_column("Family", style="white")
346
+ table.add_column("Max Tokens", style="white")
347
+ table.add_column("Streaming", style="white")
348
+
349
+ for model in models:
350
+ capabilities = model.get("capabilities", {})
351
+ family = capabilities.get("family", "N/A")
352
+ max_tokens = capabilities.get("limits", {}).get("max_output_tokens", "N/A")
353
+ streaming = capabilities.get("supports", {}).get("streaming", False)
354
+
355
+ table.add_row(
356
+ model.get("id", "N/A"),
357
+ model.get("name", "N/A"),
358
+ model.get("vendor", "N/A"),
359
+ model.get("version", "N/A"),
360
+ family,
361
+ str(max_tokens),
362
+ str(streaming),
363
+ )
364
+
365
+ console.print(table)
366
+
367
+
368
+ if __name__ == "__main__":
369
+ app()
@@ -0,0 +1,368 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+ from dataclasses import dataclass
4
+ from typing import List, Tuple, Optional, Union
5
+ from enum import Enum
6
+
7
+
8
+ class GitError(Exception):
9
+ """Base exception for git-related errors."""
10
+
11
+ pass
12
+
13
+
14
+ class NotAGitRepositoryError(GitError):
15
+ """Raised when not in a git repository."""
16
+
17
+ pass
18
+
19
+
20
+ class GitCommandError(GitError):
21
+ """Raised when a git command fails."""
22
+
23
+ pass
24
+
25
+
26
+ class FileStatus(Enum):
27
+ """Git file status codes."""
28
+
29
+ MODIFIED = "M"
30
+ ADDED = "A"
31
+ DELETED = "D"
32
+ RENAMED = "R"
33
+ COPIED = "C"
34
+ UNMERGED = "U"
35
+ UNTRACKED = "?"
36
+ IGNORED = "!"
37
+
38
+
39
+ @dataclass
40
+ class GitFile:
41
+ """Represents a file in git status."""
42
+
43
+ path: str
44
+ status: str
45
+ staged_status: str
46
+
47
+ @property
48
+ def is_staged(self) -> bool:
49
+ """Check if file has staged changes."""
50
+ return self.staged_status != " " and self.staged_status != "?"
51
+
52
+ @property
53
+ def is_modified(self) -> bool:
54
+ """Check if file is modified."""
55
+ return self.status == "M" or self.staged_status == "M"
56
+
57
+ @property
58
+ def is_untracked(self) -> bool:
59
+ """Check if file is untracked."""
60
+ return self.staged_status == "?" and self.status == "?"
61
+
62
+
63
+ @dataclass
64
+ class GitStatus:
65
+ """Structured representation of git status."""
66
+
67
+ files: List[GitFile]
68
+ staged_diff: str
69
+ unstaged_diff: str
70
+
71
+ @property
72
+ def has_staged_changes(self) -> bool:
73
+ """Check if there are any staged changes."""
74
+ return bool(self.staged_diff.strip())
75
+
76
+ @property
77
+ def has_unstaged_changes(self) -> bool:
78
+ """Check if there are any unstaged changes."""
79
+ return bool(self.unstaged_diff.strip())
80
+
81
+ @property
82
+ def has_untracked_files(self) -> bool:
83
+ """Check if there are any untracked files."""
84
+ return any(f.is_untracked for f in self.files)
85
+
86
+ @property
87
+ def staged_files(self) -> List[GitFile]:
88
+ """Get list of files with staged changes."""
89
+ return [f for f in self.files if f.is_staged]
90
+
91
+ @property
92
+ def unstaged_files(self) -> List[GitFile]:
93
+ """Get list of files with unstaged changes."""
94
+ return [f for f in self.files if not f.is_staged and not f.is_untracked]
95
+
96
+ @property
97
+ def untracked_files(self) -> List[GitFile]:
98
+ """Get list of untracked files."""
99
+ return [f for f in self.files if f.is_untracked]
100
+
101
+ def get_porcelain_output(self) -> str:
102
+ """Get the original porcelain output format."""
103
+ lines = []
104
+ for file in self.files:
105
+ lines.append(f"{file.staged_status}{file.status} {file.path}")
106
+ return "\n".join(lines)
107
+
108
+
109
+ class GitRepository:
110
+ """Encapsulates git repository operations."""
111
+
112
+ def __init__(self, repo_path: Optional[Path] = None, timeout: int = 30):
113
+ """
114
+ Initialize GitRepository.
115
+
116
+ Args:
117
+ repo_path: Path to git repository. Defaults to current directory.
118
+ timeout: Timeout for git commands in seconds.
119
+
120
+ Raises:
121
+ NotAGitRepositoryError: If the path is not a git repository.
122
+ """
123
+ self.repo_path = repo_path or Path.cwd()
124
+ self.timeout = timeout
125
+ self._validate_git_repo()
126
+
127
+ def _validate_git_repo(self) -> None:
128
+ """Ensure we're in a git repository."""
129
+ try:
130
+ self._run_git_command(["rev-parse", "--git-dir"])
131
+ except GitCommandError:
132
+ raise NotAGitRepositoryError(f"{self.repo_path} is not a git repository")
133
+
134
+ def _run_git_command(
135
+ self, args: List[str], check: bool = True
136
+ ) -> subprocess.CompletedProcess:
137
+ """
138
+ Run a git command and return the result.
139
+
140
+ Args:
141
+ args: Git command arguments (without 'git' prefix).
142
+ check: Whether to raise exception on non-zero exit code.
143
+
144
+ Returns:
145
+ CompletedProcess instance.
146
+
147
+ Raises:
148
+ GitCommandError: If command fails and check=True.
149
+ """
150
+ cmd = ["git"] + args
151
+ try:
152
+ result = subprocess.run(
153
+ cmd,
154
+ cwd=self.repo_path,
155
+ capture_output=True,
156
+ text=True,
157
+ timeout=self.timeout,
158
+ check=check,
159
+ )
160
+ return result
161
+ except subprocess.CalledProcessError as e:
162
+ raise GitCommandError(f"Git command failed: {' '.join(cmd)}\n{e.stderr}")
163
+ except subprocess.TimeoutExpired:
164
+ raise GitCommandError(f"Git command timed out: {' '.join(cmd)}")
165
+
166
+ def get_status(self) -> GitStatus:
167
+ """
168
+ Get comprehensive git status information.
169
+
170
+ Returns:
171
+ GitStatus object with all status information.
172
+ """
173
+ # Get porcelain status
174
+ status_result = self._run_git_command(["status", "--porcelain"])
175
+
176
+ # Get staged diff
177
+ staged_diff_result = self._run_git_command(["diff", "--staged"])
178
+
179
+ # Get unstaged diff
180
+ unstaged_diff_result = self._run_git_command(["diff"])
181
+
182
+ # Parse status output into GitFile objects
183
+ files = self._parse_status_output(status_result.stdout)
184
+
185
+ return GitStatus(
186
+ files=files,
187
+ staged_diff=staged_diff_result.stdout,
188
+ unstaged_diff=unstaged_diff_result.stdout,
189
+ )
190
+
191
+ def _parse_status_output(self, status_output: str) -> List[GitFile]:
192
+ """Parse git status --porcelain output into GitFile objects."""
193
+ files = []
194
+ for line in status_output.strip().split("\n"):
195
+ if not line:
196
+ continue
197
+
198
+ # Git status format: XY filename
199
+ # X = staged status, Y = unstaged status
200
+ if len(line) < 3:
201
+ continue
202
+
203
+ staged_status = line[0]
204
+ unstaged_status = line[1]
205
+ filename = line[3:] # Skip the space
206
+
207
+ files.append(
208
+ GitFile(
209
+ path=filename, status=unstaged_status, staged_status=staged_status
210
+ )
211
+ )
212
+
213
+ return files
214
+
215
+ def stage_files(self, paths: Optional[List[str]] = None) -> None:
216
+ """
217
+ Stage files for commit.
218
+
219
+ Args:
220
+ paths: List of file paths to stage. If None, stages all files (git add .).
221
+ """
222
+ if paths is None:
223
+ self._run_git_command(["add", "."])
224
+ else:
225
+ # Stage files in batches to avoid command line length limits
226
+ batch_size = 100
227
+ for i in range(0, len(paths), batch_size):
228
+ batch = paths[i : i + batch_size]
229
+ self._run_git_command(["add"] + batch)
230
+
231
+ def stage_modified(self) -> None:
232
+ """Stage all modified files (git add -u)."""
233
+ self._run_git_command(["add", "-u"])
234
+
235
+ def unstage_files(self, paths: Optional[List[str]] = None) -> None:
236
+ """
237
+ Unstage files.
238
+
239
+ Args:
240
+ paths: List of file paths to unstage. If None, unstages all files.
241
+ """
242
+ if paths is None:
243
+ self._run_git_command(["reset", "HEAD"])
244
+ else:
245
+ self._run_git_command(["reset", "HEAD"] + paths)
246
+
247
+ def commit(self, message: Optional[str] = None, use_editor: bool = False) -> str:
248
+ """
249
+ Create a commit with the given message or using git's editor.
250
+
251
+ Args:
252
+ message: Commit message. Used as template if use_editor is True.
253
+ use_editor: Whether to use git's configured editor.
254
+
255
+ Returns:
256
+ Commit SHA.
257
+
258
+ Raises:
259
+ GitCommandError: If commit fails.
260
+ """
261
+ if use_editor:
262
+ import tempfile
263
+ import os
264
+
265
+ # Create temp file with message as starting point
266
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
267
+ if message:
268
+ f.write(message)
269
+ temp_file = f.name
270
+
271
+ try:
272
+ args = ["commit", "-e", "-F", temp_file]
273
+
274
+ # Run interactively without capturing output
275
+ cmd = ["git"] + args
276
+ subprocess.run(
277
+ cmd,
278
+ cwd=self.repo_path,
279
+ timeout=self.timeout,
280
+ check=True,
281
+ )
282
+ except subprocess.CalledProcessError:
283
+ raise GitCommandError(f"Git commit failed: {' '.join(cmd)}")
284
+ except subprocess.TimeoutExpired:
285
+ raise GitCommandError(f"Git commit timed out: {' '.join(cmd)}")
286
+ finally:
287
+ # Clean up temp file
288
+ os.unlink(temp_file)
289
+ else:
290
+ if message is None:
291
+ raise ValueError("message is required when use_editor is False")
292
+ args = ["commit", "-m", message]
293
+
294
+ self._run_git_command(args)
295
+
296
+ # Extract commit SHA from output
297
+ sha_result = self._run_git_command(["rev-parse", "HEAD"])
298
+ return sha_result.stdout.strip()
299
+
300
+ def get_recent_commits(self, limit: int = 10) -> List[Tuple[str, str]]:
301
+ """
302
+ Get recent commit history.
303
+
304
+ Args:
305
+ limit: Number of commits to retrieve.
306
+
307
+ Returns:
308
+ List of tuples (sha, message).
309
+ """
310
+ result = self._run_git_command(
311
+ ["log", f"--max-count={limit}", "--pretty=format:%H|%s"]
312
+ )
313
+
314
+ commits = []
315
+ for line in result.stdout.strip().split("\n"):
316
+ if "|" in line:
317
+ sha, message = line.split("|", 1)
318
+ commits.append((sha, message))
319
+
320
+ return commits
321
+
322
+ def get_current_branch(self) -> str:
323
+ """Get the name of the current branch."""
324
+ result = self._run_git_command(["rev-parse", "--abbrev-ref", "HEAD"])
325
+ return result.stdout.strip()
326
+
327
+ def has_changes(self) -> bool:
328
+ """Check if there are any changes (staged, unstaged, or untracked)."""
329
+ status = self.get_status()
330
+ return (
331
+ status.has_staged_changes
332
+ or status.has_unstaged_changes
333
+ or status.has_untracked_files
334
+ )
335
+
336
+ def get_diff(self, staged: bool = False, paths: Optional[List[str]] = None) -> str:
337
+ """
338
+ Get diff output.
339
+
340
+ Args:
341
+ staged: Whether to get staged diff (--staged flag).
342
+ paths: Specific paths to diff.
343
+
344
+ Returns:
345
+ Diff output as string.
346
+ """
347
+ args = ["diff"]
348
+ if staged:
349
+ args.append("--staged")
350
+ if paths:
351
+ args.extend(["--"] + paths)
352
+
353
+ result = self._run_git_command(args)
354
+ return result.stdout
355
+
356
+ def get_file_content(self, path: str, revision: str = "HEAD") -> str:
357
+ """
358
+ Get content of a file at a specific revision.
359
+
360
+ Args:
361
+ path: File path.
362
+ revision: Git revision (default: HEAD).
363
+
364
+ Returns:
365
+ File content as string.
366
+ """
367
+ result = self._run_git_command(["show", f"{revision}:{path}"])
368
+ return result.stdout
@@ -0,0 +1,343 @@
1
+ version = 1
2
+ revision = 2
3
+ requires-python = ">=3.12"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.7.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "certifi"
16
+ version = "2025.7.14"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" }
19
+ wheels = [
20
+ { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" },
21
+ ]
22
+
23
+ [[package]]
24
+ name = "charset-normalizer"
25
+ version = "3.4.2"
26
+ source = { registry = "https://pypi.org/simple" }
27
+ sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
28
+ wheels = [
29
+ { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
30
+ { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
31
+ { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
32
+ { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
33
+ { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
34
+ { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
35
+ { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
36
+ { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
37
+ { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
38
+ { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
39
+ { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
40
+ { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
41
+ { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
42
+ { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
43
+ { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
44
+ { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
45
+ { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
46
+ { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
47
+ { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
48
+ { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
49
+ { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
50
+ { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
51
+ { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
52
+ { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
53
+ { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
54
+ { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
55
+ { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
56
+ ]
57
+
58
+ [[package]]
59
+ name = "click"
60
+ version = "8.2.1"
61
+ source = { registry = "https://pypi.org/simple" }
62
+ dependencies = [
63
+ { name = "colorama", marker = "sys_platform == 'win32'" },
64
+ ]
65
+ sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
66
+ wheels = [
67
+ { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
68
+ ]
69
+
70
+ [[package]]
71
+ name = "colorama"
72
+ version = "0.4.6"
73
+ source = { registry = "https://pypi.org/simple" }
74
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
75
+ wheels = [
76
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
77
+ ]
78
+
79
+ [[package]]
80
+ name = "git-copilot-commit"
81
+ version = "0.1.0"
82
+ source = { editable = "." }
83
+ dependencies = [
84
+ { name = "pycopilot" },
85
+ { name = "rich" },
86
+ { name = "typer" },
87
+ { name = "xdg-base-dirs" },
88
+ ]
89
+
90
+ [package.metadata]
91
+ requires-dist = [
92
+ { name = "pycopilot", specifier = ">=0.1.1" },
93
+ { name = "rich", specifier = ">=14.0.0" },
94
+ { name = "typer", specifier = ">=0.16.0" },
95
+ { name = "xdg-base-dirs", specifier = ">=6.0.2" },
96
+ ]
97
+
98
+ [[package]]
99
+ name = "idna"
100
+ version = "3.10"
101
+ source = { registry = "https://pypi.org/simple" }
102
+ sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
103
+ wheels = [
104
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
105
+ ]
106
+
107
+ [[package]]
108
+ name = "markdown-it-py"
109
+ version = "3.0.0"
110
+ source = { registry = "https://pypi.org/simple" }
111
+ dependencies = [
112
+ { name = "mdurl" },
113
+ ]
114
+ sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
115
+ wheels = [
116
+ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
117
+ ]
118
+
119
+ [[package]]
120
+ name = "mdurl"
121
+ version = "0.1.2"
122
+ source = { registry = "https://pypi.org/simple" }
123
+ sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
124
+ wheels = [
125
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
126
+ ]
127
+
128
+ [[package]]
129
+ name = "platformdirs"
130
+ version = "4.3.8"
131
+ source = { registry = "https://pypi.org/simple" }
132
+ sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
133
+ wheels = [
134
+ { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
135
+ ]
136
+
137
+ [[package]]
138
+ name = "prompt-toolkit"
139
+ version = "3.0.51"
140
+ source = { registry = "https://pypi.org/simple" }
141
+ dependencies = [
142
+ { name = "wcwidth" },
143
+ ]
144
+ sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" }
145
+ wheels = [
146
+ { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" },
147
+ ]
148
+
149
+ [[package]]
150
+ name = "pycopilot"
151
+ version = "0.1.1"
152
+ source = { registry = "https://pypi.org/simple" }
153
+ dependencies = [
154
+ { name = "platformdirs" },
155
+ { name = "prompt-toolkit" },
156
+ { name = "pydantic" },
157
+ { name = "questionary" },
158
+ { name = "requests" },
159
+ { name = "rich" },
160
+ { name = "typer" },
161
+ ]
162
+ sdist = { url = "https://files.pythonhosted.org/packages/56/8e/3f2633fa37a6ab712c7608ab0ca640663e45ebcecf9ec8625d2a10206598/pycopilot-0.1.1.tar.gz", hash = "sha256:9b8322d78d3c3d2546fee62f4deebd130808775862344423357f373fcfc2cd30", size = 33847, upload-time = "2025-07-17T02:19:57.341Z" }
163
+ wheels = [
164
+ { url = "https://files.pythonhosted.org/packages/c5/92/169a7a85f92c762bd3a58590f6afc36b380f82aa3938946a446bc2f039f2/pycopilot-0.1.1-py3-none-any.whl", hash = "sha256:9d3df1138a380b1d83d6f2bdf32d2aacc2443bbea6bb8f918fe804683dc19d91", size = 10565, upload-time = "2025-07-17T02:19:56.128Z" },
165
+ ]
166
+
167
+ [[package]]
168
+ name = "pydantic"
169
+ version = "2.11.7"
170
+ source = { registry = "https://pypi.org/simple" }
171
+ dependencies = [
172
+ { name = "annotated-types" },
173
+ { name = "pydantic-core" },
174
+ { name = "typing-extensions" },
175
+ { name = "typing-inspection" },
176
+ ]
177
+ sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
178
+ wheels = [
179
+ { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
180
+ ]
181
+
182
+ [[package]]
183
+ name = "pydantic-core"
184
+ version = "2.33.2"
185
+ source = { registry = "https://pypi.org/simple" }
186
+ dependencies = [
187
+ { name = "typing-extensions" },
188
+ ]
189
+ sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
190
+ wheels = [
191
+ { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
192
+ { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
193
+ { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
194
+ { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
195
+ { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
196
+ { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
197
+ { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
198
+ { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
199
+ { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
200
+ { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
201
+ { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
202
+ { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
203
+ { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
204
+ { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
205
+ { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
206
+ { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
207
+ { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
208
+ { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
209
+ { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
210
+ { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
211
+ { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
212
+ { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
213
+ { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
214
+ { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
215
+ { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
216
+ { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
217
+ { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
218
+ { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
219
+ { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
220
+ { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
221
+ { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
222
+ ]
223
+
224
+ [[package]]
225
+ name = "pygments"
226
+ version = "2.19.2"
227
+ source = { registry = "https://pypi.org/simple" }
228
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
229
+ wheels = [
230
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
231
+ ]
232
+
233
+ [[package]]
234
+ name = "questionary"
235
+ version = "2.1.0"
236
+ source = { registry = "https://pypi.org/simple" }
237
+ dependencies = [
238
+ { name = "prompt-toolkit" },
239
+ ]
240
+ sdist = { url = "https://files.pythonhosted.org/packages/a8/b8/d16eb579277f3de9e56e5ad25280fab52fc5774117fb70362e8c2e016559/questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587", size = 26775, upload-time = "2024-12-29T11:49:17.802Z" }
241
+ wheels = [
242
+ { url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747, upload-time = "2024-12-29T11:49:16.734Z" },
243
+ ]
244
+
245
+ [[package]]
246
+ name = "requests"
247
+ version = "2.32.4"
248
+ source = { registry = "https://pypi.org/simple" }
249
+ dependencies = [
250
+ { name = "certifi" },
251
+ { name = "charset-normalizer" },
252
+ { name = "idna" },
253
+ { name = "urllib3" },
254
+ ]
255
+ sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
256
+ wheels = [
257
+ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
258
+ ]
259
+
260
+ [[package]]
261
+ name = "rich"
262
+ version = "14.0.0"
263
+ source = { registry = "https://pypi.org/simple" }
264
+ dependencies = [
265
+ { name = "markdown-it-py" },
266
+ { name = "pygments" },
267
+ ]
268
+ sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" }
269
+ wheels = [
270
+ { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" },
271
+ ]
272
+
273
+ [[package]]
274
+ name = "shellingham"
275
+ version = "1.5.4"
276
+ source = { registry = "https://pypi.org/simple" }
277
+ sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
278
+ wheels = [
279
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
280
+ ]
281
+
282
+ [[package]]
283
+ name = "typer"
284
+ version = "0.16.0"
285
+ source = { registry = "https://pypi.org/simple" }
286
+ dependencies = [
287
+ { name = "click" },
288
+ { name = "rich" },
289
+ { name = "shellingham" },
290
+ { name = "typing-extensions" },
291
+ ]
292
+ sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" }
293
+ wheels = [
294
+ { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" },
295
+ ]
296
+
297
+ [[package]]
298
+ name = "typing-extensions"
299
+ version = "4.14.1"
300
+ source = { registry = "https://pypi.org/simple" }
301
+ sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
302
+ wheels = [
303
+ { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
304
+ ]
305
+
306
+ [[package]]
307
+ name = "typing-inspection"
308
+ version = "0.4.1"
309
+ source = { registry = "https://pypi.org/simple" }
310
+ dependencies = [
311
+ { name = "typing-extensions" },
312
+ ]
313
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
314
+ wheels = [
315
+ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
316
+ ]
317
+
318
+ [[package]]
319
+ name = "urllib3"
320
+ version = "2.5.0"
321
+ source = { registry = "https://pypi.org/simple" }
322
+ sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
323
+ wheels = [
324
+ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
325
+ ]
326
+
327
+ [[package]]
328
+ name = "wcwidth"
329
+ version = "0.2.13"
330
+ source = { registry = "https://pypi.org/simple" }
331
+ sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" }
332
+ wheels = [
333
+ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" },
334
+ ]
335
+
336
+ [[package]]
337
+ name = "xdg-base-dirs"
338
+ version = "6.0.2"
339
+ source = { registry = "https://pypi.org/simple" }
340
+ sdist = { url = "https://files.pythonhosted.org/packages/bf/d0/bbe05a15347538aaf9fa5b51ac3b97075dfb834931fcb77d81fbdb69e8f6/xdg_base_dirs-6.0.2.tar.gz", hash = "sha256:950504e14d27cf3c9cb37744680a43bf0ac42efefc4ef4acf98dc736cab2bced", size = 4085, upload-time = "2024-10-19T14:35:08.114Z" }
341
+ wheels = [
342
+ { url = "https://files.pythonhosted.org/packages/fc/03/030b47fd46b60fc87af548e57ff59c2ca84b2a1dadbe721bb0ce33896b2e/xdg_base_dirs-6.0.2-py3-none-any.whl", hash = "sha256:3c01d1b758ed4ace150ac960ac0bd13ce4542b9e2cdf01312dcda5012cfebabe", size = 4747, upload-time = "2024-10-19T14:35:05.931Z" },
343
+ ]