diffron 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.
diffron/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """
2
+ Diffron - AI-powered Git automation with Lemonade.
3
+
4
+ Automatically generates commit messages and PR descriptions using
5
+ your local Lemonade LLM server.
6
+ """
7
+
8
+ from .client import DiffronClient
9
+ from .lemonade import LemonadeClient, detect_lemonade_port
10
+ from .commit_gen import generate_commit_message
11
+ from .pr_gen import generate_pr_description, PRDescription
12
+ from .git_hooks import install_hooks, uninstall_hooks, is_hooks_installed
13
+
14
+ __version__ = "0.1.0"
15
+ __author__ = "Diffron Contributors"
16
+ __all__ = [
17
+ "DiffronClient",
18
+ "LemonadeClient",
19
+ "detect_lemonade_port",
20
+ "generate_commit_message",
21
+ "generate_pr_description",
22
+ "PRDescription",
23
+ "install_hooks",
24
+ "uninstall_hooks",
25
+ "is_hooks_installed",
26
+ ]
diffron/__init__.pyi ADDED
@@ -0,0 +1,28 @@
1
+ """
2
+ Diffron - AI-powered Git automation with Lemonade.
3
+
4
+ Automatically generates commit messages and PR descriptions using
5
+ your local Lemonade LLM server.
6
+ """
7
+
8
+ from typing import Optional
9
+ from .client import DiffronClient
10
+ from .lemonade import LemonadeClient
11
+ from .commit_gen import generate_commit_message
12
+ from .pr_gen import generate_pr_description, PRDescription
13
+ from .git_hooks import install_hooks, uninstall_hooks, is_hooks_installed
14
+
15
+ __version__: str
16
+ __author__: str
17
+
18
+ __all__ = [
19
+ "DiffronClient",
20
+ "LemonadeClient",
21
+ "detect_lemonade_port",
22
+ "generate_commit_message",
23
+ "generate_pr_description",
24
+ "PRDescription",
25
+ "install_hooks",
26
+ "uninstall_hooks",
27
+ "is_hooks_installed",
28
+ ]
diffron/cli.py ADDED
@@ -0,0 +1,347 @@
1
+ """
2
+ Command-line interface for Diffron.
3
+
4
+ Provides CLI entry points for installing hooks, generating PRs, and checking status.
5
+ """
6
+
7
+ import argparse
8
+ import sys
9
+ from typing import Optional
10
+
11
+
12
+ def install_hooks_cli():
13
+ """CLI entry point for installing Git hooks."""
14
+ parser = argparse.ArgumentParser(
15
+ description="Install Diffron Git hooks to a repository."
16
+ )
17
+ parser.add_argument(
18
+ "--global",
19
+ dest="global_install",
20
+ action="store_true",
21
+ help="Install hooks globally for all repositories.",
22
+ )
23
+ parser.add_argument(
24
+ "--repo",
25
+ "-r",
26
+ type=str,
27
+ default=".",
28
+ help="Path to the git repository (default: current directory).",
29
+ )
30
+
31
+ args = parser.parse_args()
32
+
33
+ from .git_hooks import install_hooks
34
+
35
+ try:
36
+ success = install_hooks(
37
+ repo_path=args.repo,
38
+ global_install=args.global_install,
39
+ )
40
+ if success:
41
+ if args.global_install:
42
+ print("Diffron hooks installed globally for all repositories.")
43
+ else:
44
+ print(f"Diffron hooks installed to: {args.repo}")
45
+ sys.exit(0)
46
+ else:
47
+ print("Failed to install hooks.", file=sys.stderr)
48
+ sys.exit(1)
49
+ except ValueError as e:
50
+ print(f"Error: {e}", file=sys.stderr)
51
+ sys.exit(1)
52
+ except Exception as e:
53
+ print(f"Unexpected error: {e}", file=sys.stderr)
54
+ sys.exit(1)
55
+
56
+
57
+ def uninstall_hooks_cli():
58
+ """CLI entry point for uninstalling Git hooks."""
59
+ parser = argparse.ArgumentParser(
60
+ description="Remove Diffron Git hooks from a repository."
61
+ )
62
+ parser.add_argument(
63
+ "--global",
64
+ dest="global_install",
65
+ action="store_true",
66
+ help="Remove global hooks configuration.",
67
+ )
68
+ parser.add_argument(
69
+ "--repo",
70
+ "-r",
71
+ type=str,
72
+ default=".",
73
+ help="Path to the git repository (default: current directory).",
74
+ )
75
+
76
+ args = parser.parse_args()
77
+
78
+ from .git_hooks import uninstall_hooks
79
+
80
+ try:
81
+ success = uninstall_hooks(
82
+ repo_path=args.repo,
83
+ global_install=args.global_install,
84
+ )
85
+ if success:
86
+ if args.global_install:
87
+ print("Diffron global hooks configuration removed.")
88
+ else:
89
+ print(f"Diffron hooks removed from: {args.repo}")
90
+ sys.exit(0)
91
+ else:
92
+ print("No hooks found to remove.", file=sys.stderr)
93
+ sys.exit(1)
94
+ except ValueError as e:
95
+ print(f"Error: {e}", file=sys.stderr)
96
+ sys.exit(1)
97
+ except Exception as e:
98
+ print(f"Unexpected error: {e}", file=sys.stderr)
99
+ sys.exit(1)
100
+
101
+
102
+ def pr_description_cli():
103
+ """CLI entry point for generating PR descriptions."""
104
+ parser = argparse.ArgumentParser(
105
+ description="Generate GitHub PR title and description."
106
+ )
107
+ parser.add_argument(
108
+ "--branch",
109
+ "-b",
110
+ type=str,
111
+ default=None,
112
+ help="Branch to analyze (default: current branch).",
113
+ )
114
+ parser.add_argument(
115
+ "--base",
116
+ type=str,
117
+ default=None,
118
+ help="Base branch to compare against (default: main/master).",
119
+ )
120
+ parser.add_argument(
121
+ "--create",
122
+ "-c",
123
+ action="store_true",
124
+ help="Automatically create PR on GitHub (requires gh CLI).",
125
+ )
126
+
127
+ args = parser.parse_args()
128
+
129
+ from .pr_gen import generate_pr_description, create_github_pr
130
+ from .lemonade import is_lemonade_running
131
+
132
+ if not is_lemonade_running():
133
+ print(
134
+ "Error: Lemonade server is not running.",
135
+ file=sys.stderr
136
+ )
137
+ print(
138
+ "Please start Lemonade first (e.g., 'lemonade serve').",
139
+ file=sys.stderr
140
+ )
141
+ sys.exit(1)
142
+
143
+ try:
144
+ if args.create:
145
+ pr = create_github_pr(
146
+ branch=args.branch,
147
+ base=args.base,
148
+ auto_submit=True,
149
+ )
150
+ print("PR created successfully!")
151
+ else:
152
+ pr = generate_pr_description(
153
+ branch=args.branch,
154
+ base=args.base,
155
+ )
156
+ print(pr.format_output())
157
+ sys.exit(0)
158
+ except ValueError as e:
159
+ print(f"Error: {e}", file=sys.stderr)
160
+ sys.exit(1)
161
+ except ConnectionError as e:
162
+ print(f"Connection error: {e}", file=sys.stderr)
163
+ sys.exit(1)
164
+ except RuntimeError as e:
165
+ print(f"Error: {e}", file=sys.stderr)
166
+ sys.exit(1)
167
+ except KeyboardInterrupt:
168
+ print("\nCancelled.", file=sys.stderr)
169
+ sys.exit(130)
170
+
171
+
172
+ def status_cli():
173
+ """CLI entry point for checking Lemonade and hooks status."""
174
+ parser = argparse.ArgumentParser(
175
+ description="Check Diffron status (Lemonade connection, hooks installation)."
176
+ )
177
+ parser.add_argument(
178
+ "--repo",
179
+ "-r",
180
+ type=str,
181
+ default=".",
182
+ help="Path to the git repository (default: current directory).",
183
+ )
184
+ parser.add_argument(
185
+ "--verbose",
186
+ "-v",
187
+ action="store_true",
188
+ help="Show detailed status information.",
189
+ )
190
+
191
+ args = parser.parse_args()
192
+
193
+ from .lemonade import detect_lemonade_port, is_lemonade_running
194
+ from .git_hooks import get_hooks_status, is_hooks_installed
195
+
196
+ # Check Lemonade status
197
+ print("Lemonade Server:")
198
+ if is_lemonade_running():
199
+ port = detect_lemonade_port()
200
+ print(f" ✓ Running on port {port}")
201
+ else:
202
+ print(" ✗ Not running")
203
+ print(" Start with: lemonade serve")
204
+
205
+ # Check hooks status
206
+ print("\nGit Hooks:")
207
+
208
+ if args.verbose:
209
+ status = get_hooks_status(args.repo)
210
+ print(f" Is git repo: {status['is_git_repo']}")
211
+ if status['git_dir']:
212
+ print(f" Git directory: {status['git_dir']}")
213
+ if status['hooks_dir']:
214
+ print(f" Hooks directory: {status['hooks_dir']}")
215
+ print(f" Wrapper exists: {status['wrapper_exists']}")
216
+ print(f" Python hook exists: {status['python_hook_exists']}")
217
+ print(f" Local hooks installed: {status['local_hooks_installed']}")
218
+ print(f" Global hooks configured: {status['global_hooks_configured']}")
219
+ else:
220
+ local_installed = is_hooks_installed(args.repo, check_global=False)
221
+ global_configured = is_hooks_installed(args.repo, check_global=True)
222
+
223
+ if local_installed:
224
+ print(f" ✓ Installed locally ({args.repo})")
225
+ elif global_configured:
226
+ print(" ✓ Installed globally")
227
+ else:
228
+ print(" ✗ Not installed")
229
+ print(" Install with: diffron-install-hooks")
230
+
231
+ sys.exit(0)
232
+
233
+
234
+ def main():
235
+ """Main CLI entry point."""
236
+ parser = argparse.ArgumentParser(
237
+ description="Diffron - AI-powered Git automation with Lemonade",
238
+ prog="diffron",
239
+ )
240
+ parser.add_argument(
241
+ "--version",
242
+ "-V",
243
+ action="version",
244
+ version="%(prog)s 0.1.0",
245
+ )
246
+
247
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
248
+
249
+ # Install hooks command
250
+ install_parser = subparsers.add_parser(
251
+ "install-hooks",
252
+ help="Install Git hooks to a repository",
253
+ )
254
+ install_parser.add_argument(
255
+ "--global",
256
+ dest="global_install",
257
+ action="store_true",
258
+ help="Install hooks globally for all repositories.",
259
+ )
260
+ install_parser.add_argument(
261
+ "--repo",
262
+ "-r",
263
+ type=str,
264
+ default=".",
265
+ help="Path to the git repository (default: current directory).",
266
+ )
267
+
268
+ # Uninstall hooks command
269
+ uninstall_parser = subparsers.add_parser(
270
+ "uninstall-hooks",
271
+ help="Remove Git hooks from a repository",
272
+ )
273
+ uninstall_parser.add_argument(
274
+ "--global",
275
+ dest="global_install",
276
+ action="store_true",
277
+ help="Remove global hooks configuration.",
278
+ )
279
+ uninstall_parser.add_argument(
280
+ "--repo",
281
+ "-r",
282
+ type=str,
283
+ default=".",
284
+ help="Path to the git repository (default: current directory).",
285
+ )
286
+
287
+ # PR command
288
+ pr_parser = subparsers.add_parser(
289
+ "pr",
290
+ help="Generate PR title and description",
291
+ )
292
+ pr_parser.add_argument(
293
+ "--branch",
294
+ "-b",
295
+ type=str,
296
+ default=None,
297
+ help="Branch to analyze (default: current branch).",
298
+ )
299
+ pr_parser.add_argument(
300
+ "--base",
301
+ type=str,
302
+ default=None,
303
+ help="Base branch to compare against (default: main/master).",
304
+ )
305
+ pr_parser.add_argument(
306
+ "--create",
307
+ "-c",
308
+ action="store_true",
309
+ help="Automatically create PR on GitHub (requires gh CLI).",
310
+ )
311
+
312
+ # Status command
313
+ status_parser = subparsers.add_parser(
314
+ "status",
315
+ help="Check Diffron status",
316
+ )
317
+ status_parser.add_argument(
318
+ "--repo",
319
+ "-r",
320
+ type=str,
321
+ default=".",
322
+ help="Path to the git repository (default: current directory).",
323
+ )
324
+ status_parser.add_argument(
325
+ "--verbose",
326
+ "-v",
327
+ action="store_true",
328
+ help="Show detailed status information.",
329
+ )
330
+
331
+ args = parser.parse_args()
332
+
333
+ if args.command == "install-hooks":
334
+ install_hooks_cli()
335
+ elif args.command == "uninstall-hooks":
336
+ uninstall_hooks_cli()
337
+ elif args.command == "pr":
338
+ pr_description_cli()
339
+ elif args.command == "status":
340
+ status_cli()
341
+ else:
342
+ parser.print_help()
343
+ sys.exit(0)
344
+
345
+
346
+ if __name__ == "__main__":
347
+ main()
diffron/cli.pyi ADDED
@@ -0,0 +1,30 @@
1
+ """
2
+ Command-line interface for Diffron.
3
+
4
+ Provides CLI entry points for installing hooks, generating PRs, and checking status.
5
+ """
6
+
7
+
8
+ def install_hooks_cli():
9
+ """CLI entry point for installing Git hooks."""
10
+ ...
11
+
12
+
13
+ def uninstall_hooks_cli():
14
+ """CLI entry point for uninstalling Git hooks."""
15
+ ...
16
+
17
+
18
+ def pr_description_cli():
19
+ """CLI entry point for generating PR descriptions."""
20
+ ...
21
+
22
+
23
+ def status_cli():
24
+ """CLI entry point for checking Lemonade and hooks status."""
25
+ ...
26
+
27
+
28
+ def main():
29
+ """Main CLI entry point."""
30
+ ...
diffron/client.py ADDED
@@ -0,0 +1,175 @@
1
+ """
2
+ Main Diffron client - unified interface for all Diffron functionality.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from .lemonade import LemonadeClient, detect_lemonade_port
8
+ from .commit_gen import generate_commit_message
9
+ from .pr_gen import generate_pr_description, PRDescription
10
+ from .git_hooks import install_hooks, uninstall_hooks, is_hooks_installed
11
+
12
+
13
+ class DiffronClient:
14
+ """
15
+ Unified client for Diffron Git automation.
16
+
17
+ Provides a single interface for commit message generation,
18
+ PR description generation, and Git hooks management.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ model: Optional[str] = None,
24
+ host: Optional[str] = None,
25
+ port: Optional[int] = None,
26
+ ):
27
+ """
28
+ Initialize Diffron client.
29
+
30
+ Args:
31
+ model: Model name to use. Auto-detects if not provided.
32
+ host: Lemonade server host. Auto-detects if not provided.
33
+ port: Lemonade server port. Auto-detects if not provided.
34
+ """
35
+ self._lemonade_client: Optional[LemonadeClient] = None
36
+ self._model = model
37
+ self._host = host
38
+ self._port = port
39
+
40
+ @property
41
+ def lemonade_client(self) -> LemonadeClient:
42
+ """Get or create Lemonade client."""
43
+ if self._lemonade_client is None:
44
+ self._lemonade_client = LemonadeClient(
45
+ host=self._host,
46
+ port=self._port,
47
+ model=self._model,
48
+ )
49
+ return self._lemonade_client
50
+
51
+ def detect_lemonade_port(self) -> Optional[int]:
52
+ """
53
+ Detect the port where Lemonade is running.
54
+
55
+ Returns:
56
+ Port number if found, None otherwise.
57
+ """
58
+ return detect_lemonade_port(host=self._host)
59
+
60
+ def generate_commit_message(
61
+ self,
62
+ diff: Optional[str] = None,
63
+ max_chars: Optional[int] = None,
64
+ max_tokens: Optional[int] = None,
65
+ temperature: Optional[float] = None,
66
+ ) -> str:
67
+ """
68
+ Generate a commit message from a git diff.
69
+
70
+ Args:
71
+ diff: Git diff string. If None, gets staged diff automatically.
72
+ max_chars: Maximum characters of diff to send.
73
+ max_tokens: Maximum tokens to generate.
74
+ temperature: Sampling temperature.
75
+
76
+ Returns:
77
+ Generated commit message in Conventional Commits format.
78
+ """
79
+ return generate_commit_message(
80
+ diff=diff,
81
+ max_chars=max_chars,
82
+ max_tokens=max_tokens,
83
+ temperature=temperature,
84
+ client=self.lemonade_client,
85
+ )
86
+
87
+ def generate_pr_description(
88
+ self,
89
+ branch: Optional[str] = None,
90
+ base: Optional[str] = None,
91
+ max_chars: Optional[int] = None,
92
+ max_tokens: Optional[int] = None,
93
+ temperature: Optional[float] = None,
94
+ ) -> PRDescription:
95
+ """
96
+ Generate a PR title and description from branch changes.
97
+
98
+ Args:
99
+ branch: Branch name to analyze. Defaults to current branch.
100
+ base: Base branch to compare against. Defaults to main/master.
101
+ max_chars: Maximum characters of diff to send.
102
+ max_tokens: Maximum tokens to generate.
103
+ temperature: Sampling temperature.
104
+
105
+ Returns:
106
+ PRDescription with generated title and description.
107
+ """
108
+ return generate_pr_description(
109
+ branch=branch,
110
+ base=base,
111
+ max_chars=max_chars,
112
+ max_tokens=max_tokens,
113
+ temperature=temperature,
114
+ client=self.lemonade_client,
115
+ )
116
+
117
+ def install_hooks(
118
+ self,
119
+ repo_path: str = ".",
120
+ global_install: bool = False,
121
+ ) -> bool:
122
+ """
123
+ Install Diffron Git hooks.
124
+
125
+ Args:
126
+ repo_path: Path to the git repository.
127
+ global_install: If True, install globally for all repositories.
128
+
129
+ Returns:
130
+ True if installation was successful.
131
+ """
132
+ return install_hooks(
133
+ repo_path=repo_path,
134
+ global_install=global_install,
135
+ )
136
+
137
+ def uninstall_hooks(
138
+ self,
139
+ repo_path: str = ".",
140
+ global_install: bool = False,
141
+ ) -> bool:
142
+ """
143
+ Remove Diffron Git hooks.
144
+
145
+ Args:
146
+ repo_path: Path to the git repository.
147
+ global_install: If True, remove global hooks configuration.
148
+
149
+ Returns:
150
+ True if uninstallation was successful.
151
+ """
152
+ return uninstall_hooks(
153
+ repo_path=repo_path,
154
+ global_install=global_install,
155
+ )
156
+
157
+ def is_hooks_installed(
158
+ self,
159
+ repo_path: str = ".",
160
+ check_global: bool = False,
161
+ ) -> bool:
162
+ """
163
+ Check if Diffron hooks are installed.
164
+
165
+ Args:
166
+ repo_path: Path to the git repository.
167
+ check_global: If True, check for global hooks configuration.
168
+
169
+ Returns:
170
+ True if hooks are installed.
171
+ """
172
+ return is_hooks_installed(
173
+ repo_path=repo_path,
174
+ check_global=check_global,
175
+ )
diffron/client.pyi ADDED
@@ -0,0 +1,80 @@
1
+ """
2
+ Main Diffron client - unified interface for all Diffron functionality.
3
+ """
4
+
5
+ from typing import Optional
6
+ from .lemonade import LemonadeClient
7
+ from .commit_gen import generate_commit_message
8
+ from .pr_gen import generate_pr_description, PRDescription
9
+
10
+
11
+ class DiffronClient:
12
+ """Unified client for Diffron Git automation."""
13
+
14
+ _lemonade_client: Optional[LemonadeClient]
15
+ _model: Optional[str]
16
+ _host: Optional[str]
17
+ _port: Optional[int]
18
+
19
+ def __init__(
20
+ self,
21
+ model: Optional[str] = None,
22
+ host: Optional[str] = None,
23
+ port: Optional[int] = None,
24
+ ):
25
+ """Initialize Diffron client."""
26
+ ...
27
+
28
+ @property
29
+ def lemonade_client(self) -> LemonadeClient:
30
+ """Get or create Lemonade client."""
31
+ ...
32
+
33
+ def detect_lemonade_port(self) -> Optional[int]:
34
+ """Detect the port where Lemonade is running."""
35
+ ...
36
+
37
+ def generate_commit_message(
38
+ self,
39
+ diff: Optional[str] = None,
40
+ max_chars: Optional[int] = None,
41
+ max_tokens: Optional[int] = None,
42
+ temperature: Optional[float] = None,
43
+ ) -> str:
44
+ """Generate a commit message from a git diff."""
45
+ ...
46
+
47
+ def generate_pr_description(
48
+ self,
49
+ branch: Optional[str] = None,
50
+ base: Optional[str] = None,
51
+ max_chars: Optional[int] = None,
52
+ max_tokens: Optional[int] = None,
53
+ temperature: Optional[float] = None,
54
+ ) -> PRDescription:
55
+ """Generate a PR title and description from branch changes."""
56
+ ...
57
+
58
+ def install_hooks(
59
+ self,
60
+ repo_path: str = ".",
61
+ global_install: bool = False,
62
+ ) -> bool:
63
+ """Install Diffron Git hooks."""
64
+ ...
65
+
66
+ def uninstall_hooks(
67
+ self,
68
+ repo_path: str = ".",
69
+ global_install: bool = False,
70
+ ) -> bool:
71
+ """Remove Diffron Git hooks."""
72
+ ...
73
+
74
+ def is_hooks_installed(
75
+ self,
76
+ repo_path: str = ".",
77
+ check_global: bool = False,
78
+ ) -> bool:
79
+ """Check if Diffron hooks are installed."""
80
+ ...