claude-commit 0.1.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,12 @@
1
+ """
2
+ claude-commit - AI-powered git commit message generator
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+ __author__ = "Your Name"
7
+ __license__ = "MIT"
8
+
9
+ from .main import generate_commit_message, main
10
+
11
+ __all__ = ["generate_commit_message", "main"]
12
+
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Configuration management for claude-commit
4
+ Handles user aliases and preferences
5
+ """
6
+
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Dict, Optional
10
+
11
+
12
+ class Config:
13
+ """Manages configuration and aliases for claude-commit"""
14
+
15
+ def __init__(self, config_path: Optional[Path] = None):
16
+ """Initialize config manager
17
+
18
+ Args:
19
+ config_path: Path to config file. Defaults to ~/.claude-commit/config.json
20
+ """
21
+ if config_path is None:
22
+ config_path = Path.home() / ".claude-commit" / "config.json"
23
+
24
+ self.config_path = config_path
25
+ self._config = self._load_config()
26
+
27
+ def _load_config(self) -> dict:
28
+ """Load configuration from file"""
29
+ if not self.config_path.exists():
30
+ return {"aliases": self._default_aliases()}
31
+
32
+ try:
33
+ with open(self.config_path, "r", encoding="utf-8") as f:
34
+ config = json.load(f)
35
+ # Ensure aliases key exists
36
+ if "aliases" not in config:
37
+ config["aliases"] = self._default_aliases()
38
+ return config
39
+ except Exception:
40
+ return {"aliases": self._default_aliases()}
41
+
42
+ def _save_config(self):
43
+ """Save configuration to file"""
44
+ # Ensure directory exists
45
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
46
+
47
+ with open(self.config_path, "w", encoding="utf-8") as f:
48
+ json.dump(self._config, f, indent=2, ensure_ascii=False)
49
+
50
+ def _default_aliases(self) -> Dict[str, str]:
51
+ """Get default command aliases (like git aliases)"""
52
+ return {
53
+ "cc": "", # just claude-commit
54
+ "cca": "--all", # analyze all changes
55
+ "ccv": "--verbose", # verbose mode
56
+ "ccc": "--commit", # auto-commit
57
+ "ccp": "--preview", # preview only
58
+ "ccac": "--all --commit", # all changes + commit
59
+ "ccav": "--all --verbose", # all changes + verbose
60
+ "ccvc": "--verbose --commit", # verbose + commit
61
+ "ccopy": "--copy", # copy to clipboard
62
+ }
63
+
64
+ @property
65
+ def aliases(self) -> Dict[str, str]:
66
+ """Get all aliases"""
67
+ return self._config.get("aliases", {})
68
+
69
+ def get_alias(self, alias: str) -> Optional[str]:
70
+ """Get command for an alias
71
+
72
+ Args:
73
+ alias: The alias name
74
+
75
+ Returns:
76
+ Command string or None if alias doesn't exist
77
+ """
78
+ return self.aliases.get(alias)
79
+
80
+ def set_alias(self, alias: str, command: str):
81
+ """Set or update an alias
82
+
83
+ Args:
84
+ alias: The alias name
85
+ command: The command arguments (without 'claude-commit')
86
+ """
87
+ self._config["aliases"][alias] = command
88
+ self._save_config()
89
+
90
+ def delete_alias(self, alias: str) -> bool:
91
+ """Delete an alias
92
+
93
+ Args:
94
+ alias: The alias name
95
+
96
+ Returns:
97
+ True if alias was deleted, False if it didn't exist
98
+ """
99
+ if alias in self._config["aliases"]:
100
+ del self._config["aliases"][alias]
101
+ self._save_config()
102
+ return True
103
+ return False
104
+
105
+ def list_aliases(self) -> Dict[str, str]:
106
+ """List all aliases"""
107
+ return self.aliases.copy()
108
+
109
+ def is_first_run(self) -> bool:
110
+ """Check if this is the first run"""
111
+ return not self.config_path.exists()
112
+
113
+ def mark_first_run_complete(self):
114
+ """Mark that first run is complete"""
115
+ if not self.config_path.exists():
116
+ self._save_config()
117
+
118
+
119
+ def resolve_alias(args: list) -> list:
120
+ """Resolve alias if first argument is an alias
121
+
122
+ Args:
123
+ args: Command line arguments
124
+
125
+ Returns:
126
+ Resolved arguments (with alias expanded)
127
+ """
128
+ if not args:
129
+ return args
130
+
131
+ config = Config()
132
+ first_arg = args[0]
133
+
134
+ # Check if first argument is an alias
135
+ alias_cmd = config.get_alias(first_arg)
136
+ if alias_cmd is not None:
137
+ # Replace alias with its command
138
+ if alias_cmd:
139
+ # Parse the alias command into arguments
140
+ import shlex
141
+ expanded_args = shlex.split(alias_cmd)
142
+ # Combine expanded args with remaining original args
143
+ return expanded_args + args[1:]
144
+ else:
145
+ # Empty alias (just the base command)
146
+ return args[1:]
147
+
148
+ return args
149
+
claude_commit/main.py ADDED
@@ -0,0 +1,876 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ claude-commit - AI-powered git commit message generator
4
+
5
+ Analyzes your git repository changes and generates a meaningful commit message
6
+ using Claude's AI capabilities.
7
+ """
8
+
9
+ import sys
10
+ import asyncio
11
+ import argparse
12
+ from pathlib import Path
13
+ from typing import Optional
14
+ import time
15
+
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+ from rich.progress import Progress, SpinnerColumn, TextColumn
19
+ import pyperclip
20
+
21
+ from claude_agent_sdk import (
22
+ query,
23
+ ClaudeAgentOptions,
24
+ AssistantMessage,
25
+ TextBlock,
26
+ ToolUseBlock,
27
+ ToolResultBlock,
28
+ ResultMessage,
29
+ CLINotFoundError,
30
+ ProcessError,
31
+ )
32
+
33
+ from .config import Config, resolve_alias
34
+
35
+ console = Console()
36
+
37
+
38
+ SYSTEM_PROMPT = """You are an expert software engineer tasked with analyzing code changes and writing excellent git commit messages.
39
+
40
+ Your goal: Generate a clear, accurate, and meaningful commit message that captures the essence of the changes.
41
+
42
+ Available tools you can use:
43
+ - Bash: Run git commands (git diff, git status, git log, etc.) and other shell commands
44
+ - Read: Read any file in the repository to understand context
45
+ - Grep: Search for patterns across files to understand relationships
46
+ - Glob: Find files matching patterns
47
+
48
+ Analysis approach (you decide what's necessary):
49
+ 1. IMPORTANT: First check recent commit history (git log -10 --oneline or git log -10 --pretty=format:"%s") to understand the existing commit message style
50
+ - Check if the project uses gitmoji (emojis like 🎉, ✨, 🐛, etc.)
51
+ - Check if messages are in Chinese, English, or other languages
52
+ - Check if they use conventional commits (feat:, fix:, etc.) or other formats
53
+ - Note any specific patterns or conventions used
54
+ 2. Examine what files changed (git status, git diff)
55
+ 3. For significant changes, READ the modified files to understand:
56
+ - The purpose and context of changed functions/classes
57
+ - How the changes fit into the larger codebase
58
+ - The intent behind the modifications
59
+ 4. Search for related code (grep) to understand dependencies and impacts
60
+ 5. Consider the scope: is this a feature, fix, refactor, docs, chore, etc.?
61
+
62
+ Commit message guidelines:
63
+ - **FOLLOW THE EXISTING FORMAT**: Match the style, language, and conventions used in recent commits
64
+ - If no clear pattern exists in history, use conventional commits format (feat:, fix:, docs:, refactor:, test:, chore:, style:, perf:)
65
+ - First line: < 50 chars (or follow existing convention), imperative mood, summarize the main change
66
+ - **IMPORTANT**: Use multi-line format with bullet points for detailed changes:
67
+ ```
68
+ type: brief summary (< 50 chars)
69
+
70
+ - First change detail
71
+ - Second change detail
72
+ - Third change detail
73
+ ```
74
+ - Be specific and meaningful (avoid vague terms like "update", "change", "modify")
75
+ - Focus on WHAT changed and WHY (the intent), not HOW (implementation details)
76
+ - Base your message on deep understanding, not just diff surface analysis
77
+
78
+ Examples of excellent commit messages (multi-line format):
79
+
80
+ Conventional commits style:
81
+ ```
82
+ feat: add user authentication system
83
+
84
+ - Implement JWT-based authentication with refresh tokens
85
+ - Add login and registration endpoints
86
+ - Create user session management
87
+ - Add password hashing with bcrypt
88
+ ```
89
+
90
+ ```
91
+ fix: prevent memory leak in connection pool
92
+
93
+ - Close idle connections after timeout
94
+ - Add connection limit configuration
95
+ - Improve error handling for failed connections
96
+ ```
97
+
98
+ With gitmoji:
99
+ ```
100
+ ✨ add user authentication system
101
+
102
+ - Implement JWT-based authentication with refresh tokens
103
+ - Add login and registration endpoints
104
+ - Create user session management
105
+ ```
106
+
107
+ In Chinese:
108
+ ```
109
+ 新增:用户认证系统
110
+
111
+ - 实现基于 JWT 的身份验证和刷新令牌
112
+ - 添加登录和注册接口
113
+ - 创建用户会话管理
114
+ ```
115
+
116
+ At the end of your analysis, output your final commit message in this format:
117
+
118
+ COMMIT_MESSAGE:
119
+ <your commit message here>
120
+
121
+ Everything between COMMIT_MESSAGE: and the end will be used as the commit message.
122
+ """
123
+
124
+
125
+ async def generate_commit_message(
126
+ repo_path: Optional[Path] = None,
127
+ staged_only: bool = True,
128
+ verbose: bool = False,
129
+ max_diff_lines: int = 500,
130
+ ) -> Optional[str]:
131
+ """
132
+ Generate a commit message based on current git changes.
133
+
134
+ Args:
135
+ repo_path: Path to git repository (defaults to current directory)
136
+ staged_only: Only analyze staged changes (git diff --cached)
137
+ verbose: Print detailed information
138
+ max_diff_lines: Maximum number of diff lines to analyze
139
+
140
+ Returns:
141
+ Generated commit message or None if failed
142
+ """
143
+ repo_path = repo_path or Path.cwd()
144
+
145
+ if verbose:
146
+ console.print(f"[blue]🔍 Analyzing repository:[/blue] {repo_path}")
147
+ console.print(f"[blue]📝 Mode:[/blue] {'staged changes only' if staged_only else 'all changes'}")
148
+
149
+ # Build the analysis prompt - give AI freedom to explore
150
+ prompt = f"""Analyze the git repository changes and generate an excellent commit message.
151
+
152
+ Context:
153
+ - Working directory: {repo_path.absolute()}
154
+ - Analysis scope: {"staged changes only (git diff --cached)" if staged_only else "all uncommitted changes (git diff)"}
155
+ - You have access to: Bash, Read, Grep, and Glob tools
156
+
157
+ Your task:
158
+ 1. **FIRST**: Check the recent commit history (e.g., `git log -3 --oneline` or `git log -3 --pretty=format:"%s"`) to understand the commit message format/style used in this project
159
+ - Does it use gitmoji? (emojis like ✨, 🐛, ♻️, etc.)
160
+ - What language? (Chinese, English, etc.)
161
+ - What format? (conventional commits, custom format, etc.)
162
+ - **IMPORTANT**: You MUST follow the same style/format/language as the existing commits
163
+ 2. Investigate the changes thoroughly. Use whatever tools and commands you need.
164
+ 3. Understand the INTENT and IMPACT of the changes, not just the surface-level diff.
165
+ 4. Read relevant files to understand context and purpose.
166
+ 5. Generate a commit message in **MULTI-LINE FORMAT** with:
167
+ - First line: brief summary (< 50 chars)
168
+ - Empty line
169
+ - Bullet points (starting with "-") for detailed changes
170
+ Example:
171
+ ```
172
+ fix: correct formatting issue
173
+
174
+ - Preserve empty lines in commit messages
175
+ - Update prompt to require multi-line format
176
+ - Add examples showing proper structure
177
+ ```
178
+
179
+ Recommendations (not requirements - use your judgment):
180
+ - Start with `git log -3 --oneline` to check the commit message style
181
+ - Then use `git status` and `git diff {"--cached" if staged_only else ""}` to see what changed
182
+ - For non-trivial changes, READ the modified files to understand their purpose
183
+ - Use grep to find related code or understand how functions are used
184
+ - Consider the broader context of the codebase
185
+
186
+ When you're confident you understand the changes, output your commit message in this exact format:
187
+
188
+ COMMIT_MESSAGE:
189
+ <your commit message>
190
+
191
+ Everything after "COMMIT_MESSAGE:" will be extracted as the final commit message.
192
+ Begin your analysis now.
193
+ """
194
+ try:
195
+ options = ClaudeAgentOptions(
196
+ system_prompt=SYSTEM_PROMPT,
197
+ allowed_tools=["Bash", "Read", "Grep", "Glob"],
198
+ permission_mode="acceptEdits",
199
+ cwd=str(repo_path.absolute()),
200
+ max_turns=10
201
+ )
202
+
203
+ if verbose:
204
+ console.print("[cyan]🔍 Claude is analyzing your changes...[/cyan]\n")
205
+ else:
206
+ console.print("[cyan]🔍 Analyzing changes...[/cyan]\n")
207
+
208
+ commit_message = None
209
+ all_text = []
210
+
211
+ # Use rich progress for spinner
212
+ progress = None
213
+ task_id = None
214
+ spinner_started = False
215
+
216
+ async for message in query(prompt=prompt, options=options):
217
+ if isinstance(message, AssistantMessage):
218
+ # Stop spinner when we get content
219
+ if progress is not None and task_id is not None:
220
+ progress.stop()
221
+ progress = None
222
+ task_id = None
223
+ spinner_started = False
224
+
225
+ for block in message.content:
226
+ if isinstance(block, TextBlock):
227
+ text = block.text.strip()
228
+ all_text.append(text)
229
+ if verbose and text:
230
+ console.print(f"[dim]💭 {text}[/dim]")
231
+
232
+ elif isinstance(block, ToolUseBlock):
233
+ # Show what tool Claude is using (simplified output)
234
+ tool_name = block.name
235
+ tool_input = block.input
236
+
237
+ if tool_name == "Bash":
238
+ cmd = tool_input.get("command", "")
239
+ if verbose:
240
+ description = tool_input.get("description", "")
241
+ if description:
242
+ console.print(f" [cyan]🔧 {cmd}[/cyan] [dim]# {description}[/dim]")
243
+ else:
244
+ console.print(f" [cyan]🔧 {cmd}[/cyan]")
245
+ else:
246
+ # Non-verbose: only show git commands and other important ones
247
+ if cmd.startswith("git "):
248
+ console.print(f" [cyan]🔧 {cmd}[/cyan]")
249
+
250
+ elif tool_name == "Read":
251
+ file_path = tool_input.get("file_path", "")
252
+ if file_path:
253
+ import os
254
+ try:
255
+ rel_path = os.path.relpath(file_path, repo_path)
256
+ if verbose:
257
+ console.print(f" [yellow]📖 Reading {rel_path}[/yellow]")
258
+ else:
259
+ # Show just filename for non-verbose
260
+ if len(rel_path) > 45:
261
+ filename = os.path.basename(rel_path)
262
+ console.print(f" [yellow]📖 {filename}[/yellow]")
263
+ else:
264
+ console.print(f" [yellow]📖 {rel_path}[/yellow]")
265
+ except:
266
+ filename = os.path.basename(file_path)
267
+ console.print(f" [yellow]📖 {filename}[/yellow]")
268
+
269
+ elif tool_name == "Grep":
270
+ pattern = tool_input.get("pattern", "")
271
+ path = tool_input.get("path", ".")
272
+ if verbose:
273
+ console.print(f" [magenta]🔍 Searching for '{pattern}' in {path}[/magenta]")
274
+ elif pattern and len(pattern) <= 40:
275
+ console.print(f" [magenta]🔍 {pattern}[/magenta]")
276
+
277
+ elif tool_name == "Glob":
278
+ pattern = tool_input.get("pattern", "")
279
+ if pattern:
280
+ if verbose:
281
+ console.print(f" [blue]📁 Finding files matching {pattern}[/blue]")
282
+ else:
283
+ console.print(f" [blue]📁 {pattern}[/blue]")
284
+
285
+ elif isinstance(block, ToolResultBlock):
286
+ # Optionally show tool results in verbose mode
287
+ if verbose and block.content:
288
+ result = str(block.content)
289
+ if len(result) > 200:
290
+ result = result[:197] + "..."
291
+ console.print(f" [dim]↳ {result}[/dim]")
292
+
293
+ # After processing all blocks, start spinner if no output in non-verbose mode
294
+ # Only start spinner once, not on every message
295
+ if not verbose and not spinner_started:
296
+ if progress is None:
297
+ progress = Progress(
298
+ SpinnerColumn(),
299
+ TextColumn("[progress.description]{task.description}"),
300
+ console=console,
301
+ transient=True,
302
+ )
303
+ progress.start()
304
+ task_id = progress.add_task("⏳ Waiting for response...", total=None)
305
+ spinner_started = True
306
+
307
+ elif isinstance(message, ResultMessage):
308
+ # Stop spinner if it's running
309
+ if progress is not None and task_id is not None:
310
+ progress.stop()
311
+ progress = None
312
+ task_id = None
313
+ spinner_started = False
314
+ console.print("\n[green]✨ Analysis complete![/green]")
315
+ if verbose:
316
+ if message.total_cost_usd:
317
+ console.print(f"[yellow]💰 Cost: ${message.total_cost_usd:.4f}[/yellow]")
318
+ console.print(f"[blue]⏱️ Duration: {message.duration_ms / 1000:.2f}s[/blue]")
319
+ console.print(f"[cyan]🔄 Turns: {message.num_turns}[/cyan]")
320
+
321
+ if not message.is_error:
322
+ # Extract commit message from COMMIT_MESSAGE: marker
323
+ full_response = "\n".join(all_text)
324
+
325
+ # Look for COMMIT_MESSAGE: marker
326
+ if "COMMIT_MESSAGE:" in full_response:
327
+ # Extract everything after COMMIT_MESSAGE:
328
+ parts = full_response.split("COMMIT_MESSAGE:", 1)
329
+ if len(parts) > 1:
330
+ commit_message = parts[1].strip()
331
+ else:
332
+ # Fallback: try to extract the last meaningful text block
333
+ # Skip explanatory text and get the actual commit message
334
+ for text in reversed(all_text):
335
+ text = text.strip()
336
+ if text and not any(
337
+ text.lower().startswith(prefix)
338
+ for prefix in ["let me", "i'll", "i will", "now i", "first", "i can see"]
339
+ ):
340
+ commit_message = text
341
+ break
342
+
343
+ # Clean up markdown code blocks if present
344
+ if commit_message:
345
+ lines = commit_message.split("\n")
346
+ cleaned_lines = []
347
+ in_code_block = False
348
+
349
+ for line in lines:
350
+ if line.strip().startswith("```"):
351
+ in_code_block = not in_code_block
352
+ continue
353
+ if not in_code_block:
354
+ cleaned_lines.append(line.rstrip())
355
+
356
+ commit_message = "\n".join(cleaned_lines).strip()
357
+
358
+ # Make sure progress is stopped before returning
359
+ if progress is not None and task_id is not None:
360
+ progress.stop()
361
+
362
+ return commit_message
363
+
364
+ except CLINotFoundError:
365
+ # Stop progress on error
366
+ if 'progress' in locals() and progress is not None:
367
+ progress.stop()
368
+ console.print("[red]❌ Error: Claude Code CLI not found.[/red]", file=sys.stderr)
369
+ console.print("[yellow]📦 Please install it: npm install -g @anthropic-ai/claude-code[/yellow]", file=sys.stderr)
370
+ return None
371
+ except ProcessError as e:
372
+ if 'progress' in locals() and progress is not None:
373
+ progress.stop()
374
+ console.print(f"[red]❌ Process error: {e}[/red]", file=sys.stderr)
375
+ if e.stderr:
376
+ console.print(f" stderr: {e.stderr}", file=sys.stderr)
377
+ return None
378
+ except Exception as e:
379
+ if 'progress' in locals() and progress is not None:
380
+ progress.stop()
381
+ console.print(f"[red]❌ Unexpected error: {e}[/red]", file=sys.stderr)
382
+ if verbose:
383
+ import traceback
384
+
385
+ traceback.print_exc()
386
+ return None
387
+
388
+
389
+ def handle_alias_command(args):
390
+ """Handle alias management subcommands"""
391
+ if len(args) == 0 or args[0] == "list":
392
+ # List all aliases
393
+ config = Config()
394
+ aliases = config.list_aliases()
395
+
396
+ if not aliases:
397
+ print("📋 No aliases configured")
398
+ return
399
+
400
+ print("📋 Configured aliases:")
401
+ print()
402
+ max_alias_len = max(len(alias) for alias in aliases.keys())
403
+
404
+ for alias, command in sorted(aliases.items()):
405
+ if command:
406
+ print(f" {alias:<{max_alias_len}} → claude-commit {command}")
407
+ else:
408
+ print(f" {alias:<{max_alias_len}} → claude-commit")
409
+
410
+ print()
411
+ print("💡 Usage: claude-commit <alias> [additional args]")
412
+ print(" Example: claude-commit cca (expands to: claude-commit --all)")
413
+ print()
414
+ print("🔧 To use aliases directly in shell (like 'ccc' instead of 'claude-commit ccc'):")
415
+ print(" Run: claude-commit alias install")
416
+
417
+ elif args[0] == "install":
418
+ # Install shell aliases
419
+ config = Config()
420
+ aliases = config.list_aliases()
421
+
422
+ if not aliases:
423
+ print("📋 No aliases configured")
424
+ return
425
+
426
+ import platform
427
+ import os
428
+
429
+ # Detect shell and platform
430
+ shell = os.environ.get("SHELL", "")
431
+ system = platform.system()
432
+
433
+ # Windows detection
434
+ if system == "Windows":
435
+ # Check if running in Git Bash (has SHELL env var on Windows)
436
+ if shell and ("bash" in shell or "sh" in shell):
437
+ # Git Bash on Windows
438
+ rc_file = Path.home() / ".bashrc"
439
+ shell_name = "bash (Git Bash)"
440
+ else:
441
+ # PowerShell (default on Windows)
442
+ # Check for PowerShell profile
443
+ ps_profile = os.environ.get("USERPROFILE", "")
444
+ if ps_profile:
445
+ # PowerShell 7+ or Windows PowerShell
446
+ rc_file = Path(ps_profile) / "Documents" / "WindowsPowerShell" / "Microsoft.PowerShell_profile.ps1"
447
+ # Also check PowerShell 7+
448
+ ps7_profile = Path(ps_profile) / "Documents" / "PowerShell" / "Microsoft.PowerShell_profile.ps1"
449
+ if ps7_profile.parent.exists():
450
+ rc_file = ps7_profile
451
+ shell_name = "powershell"
452
+ else:
453
+ print("⚠️ Could not detect PowerShell profile location")
454
+ print()
455
+ print(" To manually add aliases in PowerShell, add to your $PROFILE:")
456
+ print()
457
+ for alias, command in sorted(aliases.items()):
458
+ if command:
459
+ print(f' Set-Alias -Name {alias} -Value "claude-commit {command}"')
460
+ else:
461
+ print(f' Set-Alias -Name {alias} -Value "claude-commit"')
462
+ return
463
+ # Unix-like systems
464
+ elif "zsh" in shell:
465
+ rc_file = Path.home() / ".zshrc"
466
+ shell_name = "zsh"
467
+ elif "bash" in shell:
468
+ rc_file = Path.home() / ".bashrc"
469
+ # On macOS, also check .bash_profile
470
+ if system == "Darwin":
471
+ bash_profile = Path.home() / ".bash_profile"
472
+ if bash_profile.exists():
473
+ rc_file = bash_profile
474
+ shell_name = "bash"
475
+ elif "fish" in shell:
476
+ # Fish shell uses different config location
477
+ rc_file = Path.home() / ".config" / "fish" / "config.fish"
478
+ shell_name = "fish"
479
+ else:
480
+ print(f"⚠️ Unknown shell: {shell or 'not detected'}")
481
+ print(" Supported shells: bash, zsh, fish, powershell (Windows)")
482
+ print()
483
+ print(" To manually add aliases, add these lines to your shell config:")
484
+ print()
485
+ for alias, command in sorted(aliases.items()):
486
+ if command:
487
+ print(f" alias {alias}='claude-commit {command}'")
488
+ else:
489
+ print(f" alias {alias}='claude-commit'")
490
+ return
491
+
492
+ # Generate alias commands (different syntax for PowerShell)
493
+ if shell_name == "powershell":
494
+ alias_lines = ["", "# claude-commit aliases (auto-generated)"]
495
+ for alias, command in sorted(aliases.items()):
496
+ if command:
497
+ # PowerShell doesn't support Set-Alias with arguments, use function instead
498
+ alias_lines.append(f"function {alias} {{ claude-commit {command} $args }}")
499
+ else:
500
+ alias_lines.append(f"function {alias} {{ claude-commit $args }}")
501
+ alias_lines.append("")
502
+ else:
503
+ # Unix-style shells (bash, zsh, fish)
504
+ alias_lines = ["", "# claude-commit aliases (auto-generated)"]
505
+ for alias, command in sorted(aliases.items()):
506
+ if command:
507
+ alias_lines.append(f"alias {alias}='claude-commit {command}'")
508
+ else:
509
+ alias_lines.append(f"alias {alias}='claude-commit'")
510
+ alias_lines.append("")
511
+
512
+ alias_block = "\n".join(alias_lines)
513
+
514
+ print(f"📝 Generated shell aliases for {shell_name}:")
515
+ print(alias_block)
516
+ print()
517
+
518
+ # Check if aliases already exist
519
+ if rc_file.exists():
520
+ content = rc_file.read_text()
521
+ if "# claude-commit aliases" in content:
522
+ print(f"⚠️ Aliases already exist in {rc_file}")
523
+ response = input(" Replace existing aliases? [Y/n]: ").strip().lower()
524
+ if response == "n" or response == "no":
525
+ print("❌ Installation cancelled")
526
+ return
527
+
528
+ # Remove old aliases
529
+ lines = content.split("\n")
530
+ new_lines = []
531
+ skip = False
532
+ for line in lines:
533
+ if "# claude-commit aliases" in line:
534
+ skip = True
535
+ elif skip and (line.strip() == "" or not line.startswith("alias ")):
536
+ skip = False
537
+
538
+ if not skip:
539
+ new_lines.append(line)
540
+
541
+ content = "\n".join(new_lines)
542
+ else:
543
+ content = ""
544
+
545
+ # Append new aliases
546
+ new_content = content.rstrip() + alias_block + "\n"
547
+
548
+ try:
549
+ # Ensure directory exists (especially for PowerShell profile)
550
+ rc_file.parent.mkdir(parents=True, exist_ok=True)
551
+
552
+ rc_file.write_text(new_content)
553
+ print(f"✅ Aliases installed to {rc_file}")
554
+ print()
555
+
556
+ # Show activation instructions (different for PowerShell)
557
+ if shell_name == "powershell":
558
+ print("📋 To activate aliases in your current PowerShell session, run:")
559
+ print()
560
+ print(f" \033[1;36m. {rc_file}\033[0m")
561
+ print()
562
+ print("Or restart PowerShell.")
563
+ print()
564
+ print("💡 Note: You may need to run this first to allow script execution:")
565
+ print(" Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser")
566
+ else:
567
+ print("📋 To activate aliases in your current shell, run:")
568
+ print()
569
+ print(f" \033[1;36msource {rc_file}\033[0m")
570
+ print()
571
+ print("Or copy and paste this command:")
572
+ print(f" \033[1;32msource {rc_file} && echo '✅ Aliases activated! Try: ccc'\033[0m")
573
+ print()
574
+ print("💡 Aliases will be automatically available in new terminal windows.")
575
+ except Exception as e:
576
+ print(f"❌ Failed to write to {rc_file}: {e}", file=sys.stderr)
577
+ sys.exit(1)
578
+
579
+ elif args[0] == "uninstall":
580
+ # Remove shell aliases
581
+ import platform
582
+ import os
583
+
584
+ shell = os.environ.get("SHELL", "")
585
+
586
+ if "zsh" in shell:
587
+ rc_file = Path.home() / ".zshrc"
588
+ elif "bash" in shell:
589
+ rc_file = Path.home() / ".bashrc"
590
+ if platform.system() == "Darwin":
591
+ bash_profile = Path.home() / ".bash_profile"
592
+ if bash_profile.exists():
593
+ rc_file = bash_profile
594
+ else:
595
+ print(f"⚠️ Unknown shell: {shell}")
596
+ return
597
+
598
+ if not rc_file.exists():
599
+ print(f"❌ {rc_file} not found")
600
+ return
601
+
602
+ content = rc_file.read_text()
603
+
604
+ if "# claude-commit aliases" not in content:
605
+ print(f"📋 No claude-commit aliases found in {rc_file}")
606
+ return
607
+
608
+ # Remove aliases block
609
+ lines = content.split("\n")
610
+ new_lines = []
611
+ skip = False
612
+ removed = False
613
+
614
+ for line in lines:
615
+ if "# claude-commit aliases" in line:
616
+ skip = True
617
+ removed = True
618
+ elif skip and (line.strip() == "" or not line.startswith("alias ")):
619
+ skip = False
620
+
621
+ if not skip:
622
+ new_lines.append(line)
623
+
624
+ if removed:
625
+ rc_file.write_text("\n".join(new_lines))
626
+ print(f"✅ Aliases removed from {rc_file}")
627
+ print()
628
+ print("🔄 To apply changes, run:")
629
+ print(f" source {rc_file}")
630
+ print()
631
+ print(" Or open a new terminal window.")
632
+
633
+ elif args[0] == "set":
634
+ # Set an alias
635
+ if len(args) < 2:
636
+ print("❌ Error: Please provide alias name", file=sys.stderr)
637
+ print(" Usage: claude-commit alias set <name> [command]", file=sys.stderr)
638
+ sys.exit(1)
639
+
640
+ alias_name = args[1]
641
+ command = " ".join(args[2:]) if len(args) > 2 else ""
642
+
643
+ config = Config()
644
+ config.set_alias(alias_name, command)
645
+
646
+ if command:
647
+ print(f"✅ Alias '{alias_name}' set to: claude-commit {command}")
648
+ else:
649
+ print(f"✅ Alias '{alias_name}' set to: claude-commit")
650
+
651
+ elif args[0] == "unset":
652
+ # Delete an alias
653
+ if len(args) < 2:
654
+ print("❌ Error: Please provide alias name", file=sys.stderr)
655
+ print(" Usage: claude-commit alias unset <name>", file=sys.stderr)
656
+ sys.exit(1)
657
+
658
+ alias_name = args[1]
659
+ config = Config()
660
+
661
+ if config.delete_alias(alias_name):
662
+ print(f"✅ Alias '{alias_name}' removed")
663
+ else:
664
+ print(f"❌ Alias '{alias_name}' not found", file=sys.stderr)
665
+ sys.exit(1)
666
+
667
+ else:
668
+ print(f"❌ Unknown alias command: {args[0]}", file=sys.stderr)
669
+ print(" Available commands: list, set, unset, install, uninstall", file=sys.stderr)
670
+ sys.exit(1)
671
+
672
+
673
+ def show_first_run_tip():
674
+ """Show helpful tip on first run"""
675
+ welcome_text = """[bold]👋 Welcome to claude-commit![/bold]
676
+
677
+ [yellow]💡 Tip:[/yellow] Install shell aliases for faster usage:
678
+ [cyan]claude-commit alias install[/cyan]
679
+
680
+ After installation, use short commands like:
681
+ • [green]ccc[/green] → auto-commit
682
+ • [green]cca[/green] → analyze all changes
683
+ • [green]ccp[/green] → preview message
684
+
685
+ Run '[cyan]claude-commit alias list[/cyan]' to see all aliases.
686
+ """
687
+ console.print()
688
+ console.print(Panel(welcome_text, border_style="blue", padding=(1, 2)))
689
+ console.print()
690
+
691
+
692
+ def main():
693
+ """Main CLI entry point."""
694
+ # Check if this is the first run
695
+ config = Config()
696
+ if config.is_first_run() and len(sys.argv) > 1 and sys.argv[1] not in ["alias", "-h", "--help"]:
697
+ show_first_run_tip()
698
+ config.mark_first_run_complete()
699
+
700
+ # Check if first argument is 'alias' command
701
+ if len(sys.argv) > 1 and sys.argv[1] == "alias":
702
+ handle_alias_command(sys.argv[2:])
703
+ return
704
+
705
+ # Resolve any aliases in the arguments
706
+ resolved_args = resolve_alias(sys.argv[1:])
707
+
708
+ parser = argparse.ArgumentParser(
709
+ description="Generate AI-powered git commit messages using Claude",
710
+ formatter_class=argparse.RawDescriptionHelpFormatter,
711
+ epilog="""
712
+ Examples:
713
+ # Generate commit message for staged changes
714
+ claude-commit
715
+
716
+ # Generate message for all changes (staged + unstaged)
717
+ claude-commit --all
718
+
719
+ # Show verbose output with analysis details
720
+ claude-commit --verbose
721
+
722
+ # Generate message and copy to clipboard (requires pbcopy/xclip)
723
+ claude-commit --copy
724
+
725
+ # Automatically commit with generated message
726
+ claude-commit --commit
727
+
728
+ # Preview without committing
729
+ claude-commit --preview
730
+
731
+ Alias Management:
732
+ # List all aliases
733
+ claude-commit alias list
734
+
735
+ # Install shell aliases (so you can use 'ccc' directly)
736
+ claude-commit alias install
737
+
738
+ # Set a custom alias
739
+ claude-commit alias set cca --all
740
+ claude-commit alias set ccv --verbose
741
+ claude-commit alias set ccac --all --commit
742
+
743
+ # Remove an alias
744
+ claude-commit alias unset cca
745
+
746
+ # Uninstall shell aliases
747
+ claude-commit alias uninstall
748
+
749
+ # Use an alias (after install)
750
+ cca (expands to: claude-commit --all)
751
+ ccc (expands to: claude-commit --commit)
752
+ """,
753
+ )
754
+
755
+ parser.add_argument(
756
+ "-a",
757
+ "--all",
758
+ action="store_true",
759
+ help="Analyze all changes, not just staged ones",
760
+ )
761
+ parser.add_argument(
762
+ "-v",
763
+ "--verbose",
764
+ action="store_true",
765
+ help="Show detailed analysis and processing information",
766
+ )
767
+ parser.add_argument(
768
+ "-p",
769
+ "--path",
770
+ type=Path,
771
+ default=None,
772
+ help="Path to git repository (defaults to current directory)",
773
+ )
774
+ parser.add_argument(
775
+ "--max-diff-lines",
776
+ type=int,
777
+ default=500,
778
+ help="Maximum number of diff lines to analyze (default: 500)",
779
+ )
780
+ parser.add_argument(
781
+ "-c",
782
+ "--commit",
783
+ action="store_true",
784
+ help="Automatically commit with the generated message",
785
+ )
786
+ parser.add_argument(
787
+ "--copy",
788
+ action="store_true",
789
+ help="Copy the generated message to clipboard",
790
+ )
791
+ parser.add_argument(
792
+ "--preview",
793
+ action="store_true",
794
+ help="Just preview the message without any action",
795
+ )
796
+
797
+ args = parser.parse_args(resolved_args)
798
+
799
+ # Run async function
800
+ try:
801
+ commit_message = asyncio.run(
802
+ generate_commit_message(
803
+ repo_path=args.path,
804
+ staged_only=not args.all,
805
+ verbose=args.verbose,
806
+ max_diff_lines=args.max_diff_lines,
807
+ )
808
+ )
809
+ except KeyboardInterrupt:
810
+ console.print("\n[yellow]⚠️ Interrupted by user[/yellow]", file=sys.stderr)
811
+ sys.exit(130)
812
+
813
+ if not commit_message:
814
+ console.print("[red]❌ Failed to generate commit message[/red]", file=sys.stderr)
815
+ sys.exit(1)
816
+
817
+ # Display the generated message with rich formatting
818
+ console.print()
819
+ console.print(Panel(
820
+ commit_message,
821
+ title="[bold]📝 Generated Commit Message[/bold]",
822
+ border_style="green",
823
+ padding=(1, 2)
824
+ ))
825
+
826
+ # Handle different output modes
827
+ if args.preview:
828
+ console.print("\n[green]✅ Preview complete (no action taken)[/green]")
829
+ return
830
+
831
+ if args.copy:
832
+ try:
833
+ pyperclip.copy(commit_message)
834
+ console.print("\n[green]✅ Commit message copied to clipboard![/green]")
835
+ except Exception as e:
836
+ console.print(f"\n[yellow]⚠️ Failed to copy to clipboard: {e}[/yellow]", file=sys.stderr)
837
+
838
+ if args.commit:
839
+ try:
840
+ import subprocess
841
+
842
+ # Confirm before committing
843
+ response = console.input("\n[yellow]❓ Commit with this message? [Y/n]:[/yellow] ").strip().lower()
844
+ if response == "n" or response == "no":
845
+ console.print("[red]❌ Commit cancelled[/red]")
846
+ return
847
+
848
+ # Execute git commit
849
+ result = subprocess.run(
850
+ ["git", "commit", "-m", commit_message],
851
+ capture_output=True,
852
+ text=True,
853
+ check=True,
854
+ )
855
+ console.print("\n[green]✅ Successfully committed![/green]")
856
+ if result.stdout:
857
+ console.print(result.stdout)
858
+ except subprocess.CalledProcessError as e:
859
+ console.print(f"\n[red]❌ Failed to commit: {e}[/red]", file=sys.stderr)
860
+ if e.stderr:
861
+ console.print(e.stderr, file=sys.stderr)
862
+ sys.exit(1)
863
+ except Exception as e:
864
+ console.print(f"\n[red]❌ Unexpected error during commit: {e}[/red]", file=sys.stderr)
865
+ sys.exit(1)
866
+ else:
867
+ # Default: just show the command
868
+ console.print("\n[dim]💡 To commit with this message, run:[/dim]")
869
+ # Escape single quotes in the message for shell
870
+ escaped_message = commit_message.replace("'", "'\\''")
871
+ console.print(f" [cyan]git commit -m '{escaped_message}'[/cyan]")
872
+ console.print("\n[dim]Or use: claude-commit --commit[/dim]")
873
+
874
+
875
+ if __name__ == "__main__":
876
+ main()
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-commit
3
+ Version: 0.1.0
4
+ Summary: AI-powered git commit message generator using Claude Agent SDK
5
+ Author-email: Johannlai <johannli666@gmail.com>
6
+ License: MIT
7
+ Keywords: git,commit,ai,claude,automation
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: claude-agent-sdk>=0.1.0
20
+ Requires-Dist: click>=8.0.0
21
+ Requires-Dist: rich>=13.0.0
22
+ Requires-Dist: pyperclip>=1.8.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
25
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
26
+ Requires-Dist: black>=23.0.0; extra == "dev"
27
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # claude-commit
31
+
32
+ 🤖 AI-powered git commit message generator using Claude Agent SDK and Claude Code CLI
33
+
34
+ ## What is this?
35
+
36
+ `claude-commit` uses Claude AI to analyze your code changes and write meaningful commit messages. Claude reads your files, understands the context, and generates commit messages following best practices.
37
+
38
+ ## Quick Start
39
+
40
+ **Install:**
41
+ ```bash
42
+ pip install claude-commit
43
+
44
+ # Required dependency
45
+ npm install -g @anthropic-ai/claude-code
46
+ ```
47
+
48
+ **Use:**
49
+ ```bash
50
+ git add .
51
+ claude-commit --commit
52
+ ```
53
+
54
+ That's it! Claude will analyze your changes and create a commit.
55
+
56
+ ## Installation
57
+
58
+ ### Prerequisites
59
+
60
+ - Python 3.10+
61
+ - Node.js
62
+ - Git
63
+
64
+ ### Install Steps
65
+
66
+ ```bash
67
+ # 1. Install Claude Code CLI (required)
68
+ npm install -g @anthropic-ai/claude-code
69
+
70
+ # 2. Install claude-commit
71
+ pip install claude-commit
72
+
73
+ # Or use pipx for isolation
74
+ pipx install claude-commit
75
+ ```
76
+
77
+ ### Authentication
78
+
79
+ `claude-commit` supports two ways to authenticate with Claude:
80
+
81
+ **Option 1: [Official Claude Code Login](https://docs.claude.com/en/docs/claude-code/quickstart#step-2%3A-log-in-to-your-account) (Recommended)**
82
+
83
+ **Option 2: Custom API Endpoint (Environment Variables)**
84
+
85
+ For custom Claude API endpoints or proxies, set these environment variables:
86
+
87
+ ```bash
88
+ # Required: Set custom endpoint and credentials
89
+ export ANTHROPIC_BASE_URL="https://your-endpoint.com/api/v1"
90
+ export ANTHROPIC_AUTH_TOKEN="your-auth-token"
91
+
92
+ # Optional: Specify custom model name
93
+ export ANTHROPIC_MODEL="your-model-name"
94
+
95
+ # Then use claude-commit normally
96
+ claude-commit --commit
97
+ ```
98
+
99
+ Add these to your `~/.zshrc` or `~/.bashrc` to persist across sessions.
100
+
101
+ ## Usage
102
+
103
+ ### Basic Commands
104
+
105
+ ```bash
106
+ # Generate commit message (default: staged changes only)
107
+ claude-commit
108
+
109
+ # Auto-commit with generated message
110
+ claude-commit --commit
111
+
112
+ # Include all changes (staged + unstaged)
113
+ claude-commit --all
114
+
115
+ # Copy message to clipboard
116
+ claude-commit --copy
117
+ ```
118
+
119
+ ### Common Options
120
+
121
+ | Option | Description |
122
+ | -------------------- | ---------------------------------- |
123
+ | `-a, --all` | Include unstaged changes |
124
+ | `-c, --commit` | Auto-commit with generated message |
125
+ | `--copy` | Copy message to clipboard |
126
+ | `--preview` | Preview message only |
127
+ | `-v, --verbose` | Show detailed analysis |
128
+ | `-p, --path PATH` | Specify repository path |
129
+ | `--max-diff-lines N` | Limit diff lines (default: 500) |
130
+
131
+ ## Aliases
132
+
133
+ Create shortcuts for common commands:
134
+
135
+ ### Install Shell Aliases
136
+
137
+ ```bash
138
+ # Install to your shell config
139
+ claude-commit alias install
140
+
141
+ # Activate in current terminal
142
+ source ~/.zshrc # zsh
143
+ source ~/.bashrc # bash
144
+ ```
145
+
146
+ ### Default Aliases
147
+
148
+ | Alias | Command | Description |
149
+ | ------- | ------------------------------ | ------------------- |
150
+ | `ccc` | `claude-commit --commit` | Quick commit |
151
+ | `ccp` | `claude-commit --preview` | Preview message |
152
+ | `cca` | `claude-commit --all` | Include all changes |
153
+ | `ccac` | `claude-commit --all --commit` | Commit all changes |
154
+ | `ccopy` | `claude-commit --copy` | Copy to clipboard |
155
+
156
+ After installation, just use:
157
+ ```bash
158
+ git add .
159
+ ccc # analyzes and commits
160
+ ```
161
+
162
+ ### Custom Aliases
163
+
164
+ ```bash
165
+ # Create your own aliases
166
+ claude-commit alias set quick --all --commit
167
+ claude-commit alias list
168
+ claude-commit alias unset quick
169
+ ```
170
+
171
+ ## How It Works
172
+
173
+ Claude autonomously analyzes your changes:
174
+
175
+ 1. **Reads** your modified files to understand context
176
+ 2. **Searches** the codebase for related code
177
+ 3. **Understands** the intent and impact of changes
178
+ 4. **Generates** a clear commit message following conventions
179
+
180
+ **Example:**
181
+ ```
182
+ feat: add JWT authentication
183
+
184
+ Implement secure authentication system with token refresh.
185
+ Includes login, logout, and session management.
186
+ ```
187
+
188
+ ## Examples
189
+
190
+ ### Typical Workflow
191
+
192
+ ```bash
193
+ # Make changes
194
+ git add .
195
+
196
+ # Preview message
197
+ claude-commit --preview
198
+
199
+ # Commit if satisfied
200
+ claude-commit --commit
201
+ ```
202
+
203
+ ### With Aliases
204
+
205
+ ```bash
206
+ # Make changes
207
+ git add .
208
+
209
+ # Quick commit
210
+ ccc
211
+ ```
212
+
213
+ ### Large Changes
214
+
215
+ ```bash
216
+ # Limit analysis for faster results
217
+ claude-commit --max-diff-lines 200 --commit
218
+ ```
219
+
220
+ ## Configuration
221
+
222
+ Configuration files:
223
+ - Aliases: `~/.claude-commit/config.json`
224
+ - Shell integration: `~/.zshrc`, `~/.bashrc`, or `$PROFILE`
225
+
226
+ ## Platform Support
227
+
228
+ | Platform | Status | Shells |
229
+ | -------- | ------ | -------------------- |
230
+ | macOS | ✅ | zsh, bash, fish |
231
+ | Linux | ✅ | bash, zsh, fish |
232
+ | Windows | ✅ | PowerShell, Git Bash |
233
+
234
+ **Windows PowerShell** first-time setup:
235
+ ```powershell
236
+ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
237
+ ```
238
+
239
+ ## Troubleshooting
240
+
241
+ **Claude Code not found?**
242
+ ```bash
243
+ npm install -g @anthropic-ai/claude-code
244
+ ```
245
+
246
+ **No changes detected?**
247
+ ```bash
248
+ git add . # stage changes
249
+ # or
250
+ claude-commit --all # include unstaged
251
+ ```
252
+
253
+ **Analysis too slow?**
254
+ ```bash
255
+ claude-commit --max-diff-lines 200
256
+ ```
257
+
258
+ ## Development
259
+
260
+ ```bash
261
+ # Clone and setup
262
+ git clone https://github.com/yourusername/claude-commit.git
263
+ cd claude-commit
264
+ python -m venv venv
265
+ source venv/bin/activate
266
+ pip install -e ".[dev]"
267
+
268
+ # Run tests
269
+ pytest tests/
270
+ ```
271
+
272
+ ## Contributing
273
+
274
+ Contributions welcome! Please:
275
+ 1. Fork the repository
276
+ 2. Create a feature branch
277
+ 3. Make your changes
278
+ 4. Submit a Pull Request
279
+
280
+ ## License
281
+
282
+ MIT License - see [LICENSE](LICENSE) file
283
+
284
+ ## Links
285
+
286
+ - [Claude Agent SDK](https://docs.anthropic.com/en/docs/claude-code/agent-sdk)
287
+ - [Conventional Commits](https://www.conventionalcommits.org/)
288
+ - [Issue Tracker](https://github.com/yourusername/claude-commit/issues)
289
+
290
+ ---
291
+
292
+ Made with ❤️ by [Johann Lai](https://x.com/programerjohann)
@@ -0,0 +1,9 @@
1
+ claude_commit/__init__.py,sha256=6fjmGt5STBtXIsuMl_tyAuzFVD11gKIzGAi5DKfhfzY,229
2
+ claude_commit/config.py,sha256=FH2cuzliS4MLosoxinlIgVsPNE5Hh-wmQO3oSlvPNNU,4598
3
+ claude_commit/main.py,sha256=6nMc6rM3muYPcLAdrMq-UiHR2zCAk3v6DTYyKKg31HM,34258
4
+ claude_commit-0.1.0.dist-info/licenses/LICENSE,sha256=99lkvv3fc8S64oQW5l4XpX9l-Q_NJaGeI7SV1sWh8f8,1084
5
+ claude_commit-0.1.0.dist-info/METADATA,sha256=8goUwmYztrSOhk5lxcYELEmGXQIvm6j5jeynogy0rhk,6837
6
+ claude_commit-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ claude_commit-0.1.0.dist-info/entry_points.txt,sha256=yHa9XQ5bPe_MxLGKWFG7XvmDfCB6weRZFDcEvdZG74k,58
8
+ claude_commit-0.1.0.dist-info/top_level.txt,sha256=e6emPTq4dNMZ3nUn4PFyxiOVNJuKXE_82ft2BqDjQZ4,14
9
+ claude_commit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ claude-commit = claude_commit.main:main
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 claude-commit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1 @@
1
+ claude_commit