mcli-framework 7.1.3__py3-none-any.whl → 7.2.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.

Potentially problematic release.


This version of mcli-framework might be problematic. Click here for more details.

Files changed (38) hide show
  1. mcli/app/main.py +10 -0
  2. mcli/lib/custom_commands.py +424 -0
  3. mcli/lib/paths.py +12 -0
  4. mcli/ml/dashboard/app.py +13 -13
  5. mcli/ml/dashboard/app_integrated.py +1292 -148
  6. mcli/ml/dashboard/app_supabase.py +46 -21
  7. mcli/ml/dashboard/app_training.py +14 -14
  8. mcli/ml/dashboard/components/charts.py +258 -0
  9. mcli/ml/dashboard/components/metrics.py +125 -0
  10. mcli/ml/dashboard/components/tables.py +228 -0
  11. mcli/ml/dashboard/pages/cicd.py +382 -0
  12. mcli/ml/dashboard/pages/predictions_enhanced.py +820 -0
  13. mcli/ml/dashboard/pages/scrapers_and_logs.py +1060 -0
  14. mcli/ml/dashboard/pages/workflows.py +533 -0
  15. mcli/ml/training/train_model.py +569 -0
  16. mcli/self/self_cmd.py +322 -94
  17. mcli/workflow/politician_trading/data_sources.py +259 -1
  18. mcli/workflow/politician_trading/models.py +159 -1
  19. mcli/workflow/politician_trading/scrapers_corporate_registry.py +846 -0
  20. mcli/workflow/politician_trading/scrapers_free_sources.py +516 -0
  21. mcli/workflow/politician_trading/scrapers_third_party.py +391 -0
  22. mcli/workflow/politician_trading/seed_database.py +539 -0
  23. mcli/workflow/workflow.py +8 -27
  24. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/METADATA +1 -1
  25. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/RECORD +29 -25
  26. mcli/workflow/daemon/api_daemon.py +0 -800
  27. mcli/workflow/daemon/commands.py +0 -1196
  28. mcli/workflow/dashboard/dashboard_cmd.py +0 -120
  29. mcli/workflow/file/file.py +0 -100
  30. mcli/workflow/git_commit/commands.py +0 -430
  31. mcli/workflow/politician_trading/commands.py +0 -1939
  32. mcli/workflow/scheduler/commands.py +0 -493
  33. mcli/workflow/sync/sync_cmd.py +0 -437
  34. mcli/workflow/videos/videos.py +0 -242
  35. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/WHEEL +0 -0
  36. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/entry_points.txt +0 -0
  37. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/licenses/LICENSE +0 -0
  38. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/top_level.txt +0 -0
@@ -1,120 +0,0 @@
1
- """ML Dashboard commands for mcli."""
2
-
3
- import subprocess
4
- import sys
5
- from pathlib import Path
6
-
7
- import click
8
-
9
- from mcli.lib.logger.logger import get_logger
10
-
11
- logger = get_logger(__name__)
12
-
13
-
14
- @click.group(name="dashboard")
15
- def dashboard():
16
- """ML monitoring dashboard commands."""
17
- pass
18
-
19
-
20
- @dashboard.command()
21
- @click.option("--port", "-p", default=8501, help="Port to run dashboard on")
22
- @click.option("--host", "-h", default="localhost", help="Host to bind to")
23
- @click.option("--debug", is_flag=True, help="Run in debug mode")
24
- def launch(port, host, debug):
25
- """Launch the ML monitoring dashboard."""
26
-
27
- click.echo(f"🚀 Starting ML Dashboard on http://{host}:{port}")
28
-
29
- # Get the dashboard app path - use Supabase version to avoid joblib issues
30
- dashboard_path = Path(__file__).parent.parent.parent / "ml" / "dashboard" / "app_supabase.py"
31
-
32
- if not dashboard_path.exists():
33
- click.echo("❌ Dashboard app not found!")
34
- logger.error(f"Dashboard app not found at {dashboard_path}")
35
- sys.exit(1)
36
-
37
- # Build streamlit command
38
- cmd = [
39
- sys.executable,
40
- "-m",
41
- "streamlit",
42
- "run",
43
- str(dashboard_path),
44
- "--server.port",
45
- str(port),
46
- "--server.address",
47
- host,
48
- "--browser.gatherUsageStats",
49
- "false",
50
- ]
51
-
52
- if debug:
53
- cmd.extend(["--logger.level", "debug"])
54
-
55
- click.echo("📊 Dashboard is starting...")
56
- click.echo("Press Ctrl+C to stop")
57
-
58
- try:
59
- subprocess.run(cmd, check=True)
60
- except KeyboardInterrupt:
61
- click.echo("\n⏹️ Dashboard stopped")
62
- except subprocess.CalledProcessError as e:
63
- click.echo(f"❌ Failed to start dashboard: {e}")
64
- logger.error(f"Dashboard failed to start: {e}")
65
- sys.exit(1)
66
-
67
-
68
- @dashboard.command()
69
- def info():
70
- """Show dashboard information and status."""
71
-
72
- click.echo("📊 ML Dashboard Information")
73
- click.echo("━" * 40)
74
-
75
- # Check if dependencies are installed
76
- try:
77
- import plotly
78
- import streamlit
79
-
80
- click.echo("✅ Dashboard dependencies installed")
81
- click.echo(f" Streamlit version: {streamlit.__version__}")
82
- click.echo(f" Plotly version: {plotly.__version__}")
83
- except ImportError as e:
84
- click.echo(f"❌ Missing dependencies: {e}")
85
- click.echo(" Run: uv sync --extra dashboard")
86
-
87
- # Check database connection
88
- try:
89
- from mcli.ml.config import settings
90
-
91
- click.echo(f"\n📁 Database URL: {settings.database.url}")
92
- click.echo(f"📍 Redis URL: {settings.redis.url}")
93
- except Exception as e:
94
- click.echo(f"⚠️ Configuration not available: {e}")
95
-
96
- click.echo("\n💡 Quick start:")
97
- click.echo(" mcli workflow dashboard launch")
98
- click.echo(" mcli workflow dashboard launch --port 8502 --host 0.0.0.0")
99
-
100
-
101
- @dashboard.command()
102
- @click.argument("action", type=click.Choice(["start", "stop", "restart"]))
103
- def service(action):
104
- """Manage dashboard as a background service."""
105
-
106
- if action == "start":
107
- click.echo("🚀 Starting dashboard service...")
108
- # Could implement systemd or pm2 integration here
109
- click.echo("⚠️ Service mode not yet implemented")
110
- click.echo(" Use 'mcli workflow dashboard launch' instead")
111
- elif action == "stop":
112
- click.echo("⏹️ Stopping dashboard service...")
113
- click.echo("⚠️ Service mode not yet implemented")
114
- elif action == "restart":
115
- click.echo("🔄 Restarting dashboard service...")
116
- click.echo("⚠️ Service mode not yet implemented")
117
-
118
-
119
- if __name__ == "__main__":
120
- dashboard()
@@ -1,100 +0,0 @@
1
- import click
2
- import fitz # PyMuPDF
3
-
4
-
5
- @click.group(name="file")
6
- def file():
7
- """Personal file utility to use with custom and/or default file system paths."""
8
- pass
9
-
10
-
11
- @file.command()
12
- @click.argument("input_oxps", type=click.Path(exists=True))
13
- @click.argument("output_pdf", type=click.Path())
14
- def oxps_to_pdf(input_oxps, output_pdf):
15
- """Converts an OXPS file (INPUT_OXPS) to a PDF file (OUTPUT_PDF)."""
16
- try:
17
- # Open the OXPS file
18
- oxps_document = fitz.open(input_oxps)
19
-
20
- # Convert to PDF bytes
21
- pdf_bytes = oxps_document.convert_to_pdf()
22
-
23
- # Open the PDF bytes as a new PDF document
24
- pdf_document = fitz.open("pdf", pdf_bytes)
25
-
26
- # Save the PDF document to a file
27
- pdf_document.save(output_pdf)
28
-
29
- click.echo(f"Successfully converted '{input_oxps}' to '{output_pdf}'")
30
-
31
- except Exception as e:
32
- click.echo(f"Error converting file: {e}", err=True)
33
-
34
-
35
- import os
36
- import subprocess
37
- from pathlib import Path
38
- from typing import List, Optional
39
-
40
- DEFAULT_DIRS = ["~/repos/lefv-vault", "~/Documents/OneDrive", "~/Documents/Documents"]
41
-
42
-
43
- @file.command(name="search")
44
- @click.argument("search-string", type=str)
45
- @click.argument("search-dirs", default=DEFAULT_DIRS)
46
- @click.argument("context-lines", default=3, type=int)
47
- def find_string_with_fzf(
48
- search_string: str = "foo",
49
- search_dirs: Optional[List[str]] = DEFAULT_DIRS,
50
- context_lines: int = 3,
51
- ) -> List[str]:
52
- """
53
- Search for a string with ripgrep in given directories and select matches with fzf.
54
-
55
- Parameters:
56
- search_string (str): The string to search for.
57
- search_dirs (Optional[List[str]]): Directories to search in. Defaults to a predefined list.
58
- context_lines (int): Number of lines of context above and below the match.
59
-
60
- Returns:
61
- List[str]: List of selected lines with context from fzf.
62
- """
63
- if not search_string.strip():
64
- raise ValueError("Search string cannot be empty")
65
-
66
- dirs_to_search = search_dirs or DEFAULT_DIRS
67
- expanded_dirs = [str(Path(d).expanduser()) for d in dirs_to_search]
68
-
69
- # Validate directories exist
70
- valid_dirs = [d for d in expanded_dirs if Path(d).exists()]
71
- if not valid_dirs:
72
- raise FileNotFoundError("None of the provided or default directories exist")
73
-
74
- # Run ripgrep with context lines
75
- rg_command = ["rg", "--color=always", f"-C{context_lines}", search_string, *valid_dirs]
76
-
77
- try:
78
- rg_proc = subprocess.run(rg_command, capture_output=True, text=True, check=True)
79
- except subprocess.CalledProcessError as e:
80
- print("No matches found or error running rg.")
81
- return []
82
-
83
- # Pipe the output through fzf
84
- try:
85
- fzf_proc = subprocess.run(
86
- ["fzf", "--ansi", "--multi"],
87
- input=rg_proc.stdout,
88
- capture_output=True,
89
- text=True,
90
- check=True,
91
- )
92
- selections = fzf_proc.stdout.strip().split("\n")
93
- return [s for s in selections if s.strip()]
94
- except subprocess.CalledProcessError:
95
- # User exited fzf without selection
96
- return []
97
-
98
-
99
- if __name__ == "__main__":
100
- file()
@@ -1,430 +0,0 @@
1
- import logging
2
- import subprocess
3
- from pathlib import Path
4
- from typing import Any, Dict, List, Optional
5
-
6
- import click
7
-
8
- from mcli.lib.logger.logger import get_logger
9
-
10
- from .ai_service import GitCommitAIService
11
-
12
- logger = get_logger(__name__)
13
-
14
-
15
- class GitCommitWorkflow:
16
- """Workflow for automatic git commit message generation"""
17
-
18
- def __init__(self, repo_path: Optional[str] = None, use_ai: bool = True):
19
- self.repo_path = Path(repo_path) if repo_path else Path.cwd()
20
- self.use_ai = use_ai
21
- self.ai_service = GitCommitAIService() if use_ai else None
22
- self.validate_git_repo()
23
-
24
- def validate_git_repo(self):
25
- """Validate that we're in a git repository"""
26
- git_dir = self.repo_path / ".git"
27
- if not git_dir.exists():
28
- raise ValueError(f"Not a git repository: {self.repo_path}")
29
-
30
- def get_git_status(self) -> Dict[str, Any]:
31
- """Get current git status"""
32
- try:
33
- result = subprocess.run(
34
- ["git", "status", "--porcelain"],
35
- cwd=self.repo_path,
36
- capture_output=True,
37
- text=True,
38
- check=True,
39
- )
40
-
41
- status_lines = result.stdout.strip().split("\n") if result.stdout.strip() else []
42
-
43
- changes = {"modified": [], "added": [], "deleted": [], "renamed": [], "untracked": []}
44
-
45
- for line in status_lines:
46
- if len(line) < 3:
47
- continue
48
-
49
- status_code = line[:2]
50
- filename = line[3:]
51
-
52
- if status_code[0] == "M" or status_code[1] == "M":
53
- changes["modified"].append(filename)
54
- elif status_code[0] == "A":
55
- changes["added"].append(filename)
56
- elif status_code[0] == "D":
57
- changes["deleted"].append(filename)
58
- elif status_code[0] == "R":
59
- changes["renamed"].append(filename)
60
- elif status_code == "??":
61
- changes["untracked"].append(filename)
62
-
63
- return {
64
- "has_changes": len(status_lines) > 0,
65
- "changes": changes,
66
- "total_files": len(status_lines),
67
- }
68
-
69
- except subprocess.CalledProcessError as e:
70
- raise RuntimeError(f"Failed to get git status: {e}")
71
-
72
- def get_git_diff(self) -> str:
73
- """Get git diff for all changes"""
74
- try:
75
- # Get diff for staged and unstaged changes
76
- staged_result = subprocess.run(
77
- ["git", "diff", "--cached"],
78
- cwd=self.repo_path,
79
- capture_output=True,
80
- text=True,
81
- check=True,
82
- )
83
-
84
- unstaged_result = subprocess.run(
85
- ["git", "diff"], cwd=self.repo_path, capture_output=True, text=True, check=True
86
- )
87
-
88
- diff_content = ""
89
- if staged_result.stdout:
90
- diff_content += "=== STAGED CHANGES ===\n" + staged_result.stdout + "\n"
91
- if unstaged_result.stdout:
92
- diff_content += "=== UNSTAGED CHANGES ===\n" + unstaged_result.stdout + "\n"
93
-
94
- return diff_content
95
-
96
- except subprocess.CalledProcessError as e:
97
- raise RuntimeError(f"Failed to get git diff: {e}")
98
-
99
- def stage_all_changes(self) -> bool:
100
- """Stage all changes for commit"""
101
- try:
102
- subprocess.run(["git", "add", "."], cwd=self.repo_path, check=True)
103
- logger.info("All changes staged successfully")
104
- return True
105
-
106
- except subprocess.CalledProcessError as e:
107
- logger.error(f"Failed to stage changes: {e}")
108
- return False
109
-
110
- def generate_commit_message(self, changes: Dict[str, Any], diff_content: str) -> str:
111
- """Generate a commit message using AI or fallback to rule-based generation"""
112
-
113
- if self.use_ai and self.ai_service:
114
- try:
115
- logger.info("Generating AI-powered commit message...")
116
- ai_message = self.ai_service.generate_commit_message(changes, diff_content)
117
- if ai_message:
118
- return ai_message
119
- else:
120
- logger.warning("AI service returned empty message, falling back to rule-based")
121
- except Exception as e:
122
- logger.error(f"AI commit message generation failed: {e}")
123
- logger.info("Falling back to rule-based commit message generation")
124
-
125
- # Fallback to rule-based generation
126
- return self._generate_rule_based_message(changes)
127
-
128
- def _generate_rule_based_message(self, changes: Dict[str, Any]) -> str:
129
- """Generate rule-based commit message (original implementation)"""
130
- summary_parts = []
131
-
132
- # Analyze file changes
133
- if changes["changes"]["added"]:
134
- if len(changes["changes"]["added"]) == 1:
135
- summary_parts.append(f"Add {changes['changes']['added'][0]}")
136
- else:
137
- summary_parts.append(f"Add {len(changes['changes']['added'])} new files")
138
-
139
- if changes["changes"]["modified"]:
140
- if len(changes["changes"]["modified"]) == 1:
141
- summary_parts.append(f"Update {changes['changes']['modified'][0]}")
142
- else:
143
- summary_parts.append(f"Update {len(changes['changes']['modified'])} files")
144
-
145
- if changes["changes"]["deleted"]:
146
- if len(changes["changes"]["deleted"]) == 1:
147
- summary_parts.append(f"Remove {changes['changes']['deleted'][0]}")
148
- else:
149
- summary_parts.append(f"Remove {len(changes['changes']['deleted'])} files")
150
-
151
- if changes["changes"]["renamed"]:
152
- summary_parts.append(f"Rename {len(changes['changes']['renamed'])} files")
153
-
154
- # Create commit message
155
- if not summary_parts:
156
- commit_message = "Update repository files"
157
- else:
158
- commit_message = ", ".join(summary_parts)
159
-
160
- # Add more context based on file patterns
161
- modified_files = changes["changes"]["modified"] + changes["changes"]["added"]
162
-
163
- # Check for specific file types
164
- if any(".py" in f for f in modified_files):
165
- commit_message += " (Python changes)"
166
- elif any(".js" in f or ".ts" in f for f in modified_files):
167
- commit_message += " (JavaScript/TypeScript changes)"
168
- elif any(".md" in f for f in modified_files):
169
- commit_message += " (Documentation changes)"
170
- elif any(
171
- "requirements" in f or "package.json" in f or "Cargo.toml" in f for f in modified_files
172
- ):
173
- commit_message += " (Dependencies changes)"
174
-
175
- return commit_message
176
-
177
- def create_commit(self, message: str) -> bool:
178
- """Create a git commit with the given message"""
179
- try:
180
- subprocess.run(["git", "commit", "-m", message], cwd=self.repo_path, check=True)
181
- logger.info(f"Commit created successfully: {message}")
182
- return True
183
-
184
- except subprocess.CalledProcessError as e:
185
- logger.error(f"Failed to create commit: {e}")
186
- return False
187
-
188
- def run_auto_commit(self) -> Dict[str, Any]:
189
- """Run the complete auto-commit workflow"""
190
- result = {
191
- "success": False,
192
- "message": "",
193
- "commit_hash": None,
194
- "changes_summary": {},
195
- "error": None,
196
- }
197
-
198
- try:
199
- # Check git status
200
- status = self.get_git_status()
201
- result["changes_summary"] = status
202
-
203
- if not status["has_changes"]:
204
- result["message"] = "No changes to commit"
205
- result["success"] = True
206
- return result
207
-
208
- # Get diff content
209
- diff_content = self.get_git_diff()
210
-
211
- # Stage all changes
212
- if not self.stage_all_changes():
213
- result["error"] = "Failed to stage changes"
214
- return result
215
-
216
- # Generate commit message
217
- commit_message = self.generate_commit_message(status, diff_content)
218
- result["message"] = commit_message
219
-
220
- # Create commit
221
- if self.create_commit(commit_message):
222
- # Get commit hash
223
- try:
224
- hash_result = subprocess.run(
225
- ["git", "rev-parse", "HEAD"],
226
- cwd=self.repo_path,
227
- capture_output=True,
228
- text=True,
229
- check=True,
230
- )
231
- result["commit_hash"] = hash_result.stdout.strip()
232
- except:
233
- pass
234
-
235
- result["success"] = True
236
- else:
237
- result["error"] = "Failed to create commit"
238
-
239
- except Exception as e:
240
- result["error"] = str(e)
241
- logger.error(f"Auto-commit workflow failed: {e}")
242
-
243
- return result
244
-
245
-
246
- @click.group(name="git-commit")
247
- def git_commit_cli():
248
- """AI-powered git commit message generation"""
249
- pass
250
-
251
-
252
- @git_commit_cli.command()
253
- @click.option(
254
- "--repo-path", type=click.Path(), help="Path to git repository (default: current directory)"
255
- )
256
- @click.option(
257
- "--dry-run", is_flag=True, help="Show what would be committed without actually committing"
258
- )
259
- @click.option("--no-ai", is_flag=True, help="Disable AI-powered commit message generation")
260
- @click.option("--model", type=str, help="Override AI model for commit message generation")
261
- def auto(repo_path: Optional[str], dry_run: bool, no_ai: bool, model: Optional[str]):
262
- """Automatically stage changes and create commit with AI-generated message"""
263
-
264
- try:
265
- workflow = GitCommitWorkflow(repo_path, use_ai=not no_ai)
266
-
267
- # Override AI model if specified
268
- if not no_ai and model and workflow.ai_service:
269
- workflow.ai_service.model_name = model
270
-
271
- if dry_run:
272
- # Just show what would happen
273
- status = workflow.get_git_status()
274
-
275
- if not status["has_changes"]:
276
- click.echo("No changes to commit")
277
- return
278
-
279
- click.echo("Changes that would be staged and committed:")
280
- changes = status["changes"]
281
-
282
- if changes["untracked"]:
283
- click.echo(f" New files ({len(changes['untracked'])}):")
284
- for file in changes["untracked"]:
285
- click.echo(f" + {file}")
286
-
287
- if changes["modified"]:
288
- click.echo(f" Modified files ({len(changes['modified'])}):")
289
- for file in changes["modified"]:
290
- click.echo(f" M {file}")
291
-
292
- if changes["deleted"]:
293
- click.echo(f" Deleted files ({len(changes['deleted'])}):")
294
- for file in changes["deleted"]:
295
- click.echo(f" - {file}")
296
-
297
- # Generate and show commit message
298
- diff_content = workflow.get_git_diff()
299
- commit_message = workflow.generate_commit_message(status, diff_content)
300
- click.echo(f"\nProposed commit message: {commit_message}")
301
-
302
- else:
303
- # Actually run the workflow
304
- result = workflow.run_auto_commit()
305
-
306
- if result["success"]:
307
- if result["commit_hash"]:
308
- click.echo(f"✅ Commit created successfully: {result['commit_hash'][:8]}")
309
- else:
310
- click.echo("✅ Commit created successfully")
311
-
312
- click.echo(f"Message: {result['message']}")
313
-
314
- # Show summary
315
- changes = result["changes_summary"]["changes"]
316
- total = sum(len(files) for files in changes.values())
317
- click.echo(f"Files changed: {total}")
318
-
319
- else:
320
- click.echo(f"❌ Failed to create commit: {result.get('error', 'Unknown error')}")
321
-
322
- except Exception as e:
323
- click.echo(f"❌ Error: {e}")
324
-
325
-
326
- @git_commit_cli.command()
327
- @click.option(
328
- "--repo-path", type=click.Path(), help="Path to git repository (default: current directory)"
329
- )
330
- def status(repo_path: Optional[str]):
331
- """Show current git repository status"""
332
-
333
- try:
334
- workflow = GitCommitWorkflow(repo_path)
335
- status_info = workflow.get_git_status()
336
-
337
- if not status_info["has_changes"]:
338
- click.echo("✅ Working tree is clean")
339
- return
340
-
341
- click.echo("📋 Repository Status:")
342
- changes = status_info["changes"]
343
-
344
- if changes["untracked"]:
345
- click.echo(f"\n📄 Untracked files ({len(changes['untracked'])}):")
346
- for file in changes["untracked"]:
347
- click.echo(f" ?? {file}")
348
-
349
- if changes["modified"]:
350
- click.echo(f"\n✏️ Modified files ({len(changes['modified'])}):")
351
- for file in changes["modified"]:
352
- click.echo(f" M {file}")
353
-
354
- if changes["added"]:
355
- click.echo(f"\n➕ Added files ({len(changes['added'])}):")
356
- for file in changes["added"]:
357
- click.echo(f" A {file}")
358
-
359
- if changes["deleted"]:
360
- click.echo(f"\n🗑️ Deleted files ({len(changes['deleted'])}):")
361
- for file in changes["deleted"]:
362
- click.echo(f" D {file}")
363
-
364
- if changes["renamed"]:
365
- click.echo(f"\n🔄 Renamed files ({len(changes['renamed'])}):")
366
- for file in changes["renamed"]:
367
- click.echo(f" R {file}")
368
-
369
- click.echo(f"\nTotal files with changes: {status_info['total_files']}")
370
-
371
- except Exception as e:
372
- click.echo(f"❌ Error: {e}")
373
-
374
-
375
- @git_commit_cli.command()
376
- @click.argument("message")
377
- @click.option(
378
- "--repo-path", type=click.Path(), help="Path to git repository (default: current directory)"
379
- )
380
- @click.option("--stage-all", is_flag=True, help="Stage all changes before committing")
381
- def commit(message: str, repo_path: Optional[str], stage_all: bool):
382
- """Create a commit with a custom message"""
383
-
384
- try:
385
- workflow = GitCommitWorkflow(repo_path)
386
-
387
- if stage_all:
388
- if not workflow.stage_all_changes():
389
- click.echo("❌ Failed to stage changes")
390
- return
391
-
392
- if workflow.create_commit(message):
393
- click.echo(f"✅ Commit created: {message}")
394
- else:
395
- click.echo("❌ Failed to create commit")
396
-
397
- except Exception as e:
398
- click.echo(f"❌ Error: {e}")
399
-
400
-
401
- @git_commit_cli.command()
402
- @click.option("--model", type=str, help="Override AI model for testing")
403
- def test_ai(model: Optional[str]):
404
- """Test the AI service for commit message generation"""
405
-
406
- try:
407
- from .ai_service import GitCommitAIService
408
-
409
- ai_service = GitCommitAIService()
410
-
411
- # Override model if specified
412
- if model:
413
- ai_service.model_name = model
414
-
415
- click.echo(f"🧪 Testing AI service with model: {ai_service.model_name}")
416
- click.echo(f"🌐 Ollama base URL: {ai_service.ollama_base_url}")
417
- click.echo(f"🌡️ Temperature: {ai_service.temperature}")
418
-
419
- # Test AI service
420
- if ai_service.test_ai_service():
421
- click.echo("✅ AI service test passed!")
422
- else:
423
- click.echo("❌ AI service test failed!")
424
-
425
- except Exception as e:
426
- click.echo(f"❌ Error testing AI service: {e}")
427
-
428
-
429
- if __name__ == "__main__":
430
- git_commit_cli()