mcli-framework 7.1.3__py3-none-any.whl → 7.3.1__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 (114) hide show
  1. mcli/__init__.py +160 -0
  2. mcli/__main__.py +14 -0
  3. mcli/app/__init__.py +23 -0
  4. mcli/app/main.py +10 -0
  5. mcli/app/model/__init__.py +0 -0
  6. mcli/app/video/__init__.py +5 -0
  7. mcli/chat/__init__.py +34 -0
  8. mcli/lib/__init__.py +0 -0
  9. mcli/lib/api/__init__.py +0 -0
  10. mcli/lib/auth/__init__.py +1 -0
  11. mcli/lib/config/__init__.py +1 -0
  12. mcli/lib/custom_commands.py +424 -0
  13. mcli/lib/erd/__init__.py +25 -0
  14. mcli/lib/files/__init__.py +0 -0
  15. mcli/lib/fs/__init__.py +1 -0
  16. mcli/lib/logger/__init__.py +3 -0
  17. mcli/lib/paths.py +12 -0
  18. mcli/lib/performance/__init__.py +17 -0
  19. mcli/lib/pickles/__init__.py +1 -0
  20. mcli/lib/shell/__init__.py +0 -0
  21. mcli/lib/toml/__init__.py +1 -0
  22. mcli/lib/watcher/__init__.py +0 -0
  23. mcli/ml/__init__.py +16 -0
  24. mcli/ml/api/__init__.py +30 -0
  25. mcli/ml/api/routers/__init__.py +27 -0
  26. mcli/ml/api/schemas.py +2 -2
  27. mcli/ml/auth/__init__.py +45 -0
  28. mcli/ml/auth/models.py +2 -2
  29. mcli/ml/backtesting/__init__.py +39 -0
  30. mcli/ml/cli/__init__.py +5 -0
  31. mcli/ml/cli/main.py +1 -1
  32. mcli/ml/config/__init__.py +33 -0
  33. mcli/ml/configs/__init__.py +16 -0
  34. mcli/ml/dashboard/__init__.py +12 -0
  35. mcli/ml/dashboard/app.py +13 -13
  36. mcli/ml/dashboard/app_integrated.py +1309 -148
  37. mcli/ml/dashboard/app_supabase.py +46 -21
  38. mcli/ml/dashboard/app_training.py +14 -14
  39. mcli/ml/dashboard/components/__init__.py +7 -0
  40. mcli/ml/dashboard/components/charts.py +258 -0
  41. mcli/ml/dashboard/components/metrics.py +125 -0
  42. mcli/ml/dashboard/components/tables.py +228 -0
  43. mcli/ml/dashboard/pages/__init__.py +6 -0
  44. mcli/ml/dashboard/pages/cicd.py +382 -0
  45. mcli/ml/dashboard/pages/predictions_enhanced.py +834 -0
  46. mcli/ml/dashboard/pages/scrapers_and_logs.py +1060 -0
  47. mcli/ml/dashboard/pages/test_portfolio.py +373 -0
  48. mcli/ml/dashboard/pages/trading.py +714 -0
  49. mcli/ml/dashboard/pages/workflows.py +533 -0
  50. mcli/ml/dashboard/utils.py +154 -0
  51. mcli/ml/data_ingestion/__init__.py +39 -0
  52. mcli/ml/database/__init__.py +47 -0
  53. mcli/ml/experimentation/__init__.py +29 -0
  54. mcli/ml/features/__init__.py +39 -0
  55. mcli/ml/mlops/__init__.py +33 -0
  56. mcli/ml/models/__init__.py +94 -0
  57. mcli/ml/monitoring/__init__.py +25 -0
  58. mcli/ml/optimization/__init__.py +27 -0
  59. mcli/ml/predictions/__init__.py +5 -0
  60. mcli/ml/preprocessing/__init__.py +28 -0
  61. mcli/ml/scripts/__init__.py +1 -0
  62. mcli/ml/trading/__init__.py +60 -0
  63. mcli/ml/trading/alpaca_client.py +353 -0
  64. mcli/ml/trading/migrations.py +164 -0
  65. mcli/ml/trading/models.py +418 -0
  66. mcli/ml/trading/paper_trading.py +326 -0
  67. mcli/ml/trading/risk_management.py +370 -0
  68. mcli/ml/trading/trading_service.py +480 -0
  69. mcli/ml/training/__init__.py +10 -0
  70. mcli/ml/training/train_model.py +569 -0
  71. mcli/mygroup/__init__.py +3 -0
  72. mcli/public/__init__.py +1 -0
  73. mcli/public/commands/__init__.py +2 -0
  74. mcli/self/__init__.py +3 -0
  75. mcli/self/self_cmd.py +579 -91
  76. mcli/workflow/__init__.py +0 -0
  77. mcli/workflow/daemon/__init__.py +15 -0
  78. mcli/workflow/daemon/daemon.py +21 -3
  79. mcli/workflow/dashboard/__init__.py +5 -0
  80. mcli/workflow/docker/__init__.py +0 -0
  81. mcli/workflow/file/__init__.py +0 -0
  82. mcli/workflow/gcloud/__init__.py +1 -0
  83. mcli/workflow/git_commit/__init__.py +0 -0
  84. mcli/workflow/interview/__init__.py +0 -0
  85. mcli/workflow/politician_trading/__init__.py +4 -0
  86. mcli/workflow/politician_trading/data_sources.py +259 -1
  87. mcli/workflow/politician_trading/models.py +159 -1
  88. mcli/workflow/politician_trading/scrapers_corporate_registry.py +846 -0
  89. mcli/workflow/politician_trading/scrapers_free_sources.py +516 -0
  90. mcli/workflow/politician_trading/scrapers_third_party.py +391 -0
  91. mcli/workflow/politician_trading/seed_database.py +539 -0
  92. mcli/workflow/registry/__init__.py +0 -0
  93. mcli/workflow/repo/__init__.py +0 -0
  94. mcli/workflow/scheduler/__init__.py +25 -0
  95. mcli/workflow/search/__init__.py +0 -0
  96. mcli/workflow/sync/__init__.py +5 -0
  97. mcli/workflow/videos/__init__.py +1 -0
  98. mcli/workflow/wakatime/__init__.py +80 -0
  99. mcli/workflow/workflow.py +8 -27
  100. {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/METADATA +3 -1
  101. {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/RECORD +105 -29
  102. mcli/workflow/daemon/api_daemon.py +0 -800
  103. mcli/workflow/daemon/commands.py +0 -1196
  104. mcli/workflow/dashboard/dashboard_cmd.py +0 -120
  105. mcli/workflow/file/file.py +0 -100
  106. mcli/workflow/git_commit/commands.py +0 -430
  107. mcli/workflow/politician_trading/commands.py +0 -1939
  108. mcli/workflow/scheduler/commands.py +0 -493
  109. mcli/workflow/sync/sync_cmd.py +0 -437
  110. mcli/workflow/videos/videos.py +0 -242
  111. {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/WHEEL +0 -0
  112. {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/entry_points.txt +0 -0
  113. {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/licenses/LICENSE +0 -0
  114. {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/top_level.txt +0 -0
@@ -1,493 +0,0 @@
1
- """
2
- CLI commands for the MCLI scheduler system
3
- """
4
-
5
- import json
6
- from datetime import datetime
7
- from typing import Optional
8
-
9
- import click
10
-
11
- from mcli.lib.logger.logger import get_logger
12
-
13
- from .cron_parser import CronExpression, get_next_run_times, validate_cron_expression
14
- from .job import JobStatus, JobType, ScheduledJob
15
- from .scheduler import (
16
- JobScheduler,
17
- create_desktop_cleanup_job,
18
- create_system_backup_job,
19
- create_temp_cleanup_job,
20
- )
21
-
22
- logger = get_logger(__name__)
23
-
24
- # Global scheduler instance
25
- _scheduler: Optional[JobScheduler] = None
26
-
27
-
28
- def get_scheduler() -> JobScheduler:
29
- """Get or create the global scheduler instance"""
30
- global _scheduler
31
- if _scheduler is None:
32
- _scheduler = JobScheduler()
33
- return _scheduler
34
-
35
-
36
- @click.group()
37
- def scheduler():
38
- """Robust cron-like job scheduling system"""
39
- pass
40
-
41
-
42
- @scheduler.command()
43
- @click.option("--start", is_flag=True, help="Start the scheduler")
44
- @click.option("--stop", is_flag=True, help="Stop the scheduler")
45
- @click.option("--restart", is_flag=True, help="Restart the scheduler")
46
- @click.option("--status", is_flag=True, help="Show scheduler status")
47
- def control(start: bool, stop: bool, restart: bool, status: bool):
48
- """Control the scheduler daemon"""
49
- sched = get_scheduler()
50
-
51
- if restart:
52
- click.echo("Restarting scheduler...")
53
- sched.stop()
54
- sched.start()
55
- click.echo("Scheduler restarted")
56
- elif start:
57
- click.echo("Starting scheduler...")
58
- sched.start()
59
- click.echo("Scheduler started")
60
- elif stop:
61
- click.echo("Stopping scheduler...")
62
- sched.stop()
63
- click.echo("Scheduler stopped")
64
- elif status:
65
- stats = sched.get_scheduler_stats()
66
- click.echo(f"Scheduler running: {stats['running']}")
67
- click.echo(f"Total jobs: {stats['total_jobs']}")
68
- click.echo(f"Enabled jobs: {stats['enabled_jobs']}")
69
- click.echo(f"Running jobs: {stats['running_jobs']}")
70
- else:
71
- click.echo("Use --start, --stop, --restart, or --status")
72
-
73
-
74
- @scheduler.command()
75
- @click.argument("name")
76
- @click.argument("cron_expression")
77
- @click.argument("command")
78
- @click.option(
79
- "--type",
80
- "job_type",
81
- type=click.Choice([t.value for t in JobType]),
82
- default="command",
83
- help="Job type",
84
- )
85
- @click.option("--description", default="", help="Job description")
86
- @click.option("--enabled/--disabled", default=True, help="Enable/disable job")
87
- @click.option("--max-runtime", default=3600, help="Maximum runtime in seconds")
88
- @click.option("--retry-count", default=0, help="Number of retries on failure")
89
- @click.option("--retry-delay", default=60, help="Delay between retries in seconds")
90
- @click.option("--working-dir", help="Working directory for job execution")
91
- @click.option("--env", multiple=True, help="Environment variables (KEY=VALUE)")
92
- def add(
93
- name: str,
94
- cron_expression: str,
95
- command: str,
96
- job_type: str,
97
- description: str,
98
- enabled: bool,
99
- max_runtime: int,
100
- retry_count: int,
101
- retry_delay: int,
102
- working_dir: Optional[str],
103
- env: tuple,
104
- ):
105
- """Add a new scheduled job"""
106
-
107
- # Validate cron expression
108
- if not validate_cron_expression(cron_expression):
109
- click.echo(f"Error: Invalid cron expression: {cron_expression}", err=True)
110
- return
111
-
112
- # Parse environment variables
113
- environment = {}
114
- for env_var in env:
115
- if "=" in env_var:
116
- key, value = env_var.split("=", 1)
117
- environment[key] = value
118
- else:
119
- click.echo(f"Warning: Invalid environment variable format: {env_var}")
120
-
121
- # Create job
122
- job = ScheduledJob(
123
- name=name,
124
- cron_expression=cron_expression,
125
- job_type=JobType(job_type),
126
- command=command,
127
- description=description,
128
- enabled=enabled,
129
- max_runtime=max_runtime,
130
- retry_count=retry_count,
131
- retry_delay=retry_delay,
132
- working_directory=working_dir,
133
- environment=environment,
134
- )
135
-
136
- # Add to scheduler
137
- sched = get_scheduler()
138
- if sched.add_job(job):
139
- click.echo(f"Added job: {name} ({job.id})")
140
-
141
- # Show next run times
142
- try:
143
- cron = CronExpression(cron_expression)
144
- if not cron.is_reboot:
145
- next_times = get_next_run_times(cron_expression, 3)
146
- if next_times:
147
- click.echo("Next run times:")
148
- for i, time in enumerate(next_times, 1):
149
- click.echo(f" {i}. {time.strftime('%Y-%m-%d %H:%M:%S')}")
150
- except Exception as e:
151
- logger.warning(f"Could not calculate next run times: {e}")
152
- else:
153
- click.echo("Failed to add job", err=True)
154
-
155
-
156
- @scheduler.command()
157
- @click.argument("job_id")
158
- def remove(job_id: str):
159
- """Remove a scheduled job"""
160
- sched = get_scheduler()
161
-
162
- # Find job by ID or name
163
- job = sched.get_job(job_id)
164
- if not job:
165
- # Try to find by name
166
- for j in sched.get_all_jobs():
167
- if j.name == job_id:
168
- job = j
169
- break
170
-
171
- if not job:
172
- click.echo(f"Job not found: {job_id}", err=True)
173
- return
174
-
175
- if sched.remove_job(job.id):
176
- click.echo(f"Removed job: {job.name}")
177
- else:
178
- click.echo("Failed to remove job", err=True)
179
-
180
-
181
- @scheduler.command()
182
- @click.option(
183
- "--format",
184
- "output_format",
185
- type=click.Choice(["table", "json"]),
186
- default="table",
187
- help="Output format",
188
- )
189
- @click.option("--status", "filter_status", help="Filter by job status")
190
- @click.option("--enabled-only", is_flag=True, help="Show only enabled jobs")
191
- def list(output_format: str, filter_status: Optional[str], enabled_only: bool):
192
- """List all scheduled jobs"""
193
- sched = get_scheduler()
194
- jobs = sched.get_all_jobs()
195
-
196
- # Apply filters
197
- if filter_status:
198
- try:
199
- status = JobStatus(filter_status)
200
- jobs = [job for job in jobs if job.status == status]
201
- except ValueError:
202
- click.echo(f"Invalid status: {filter_status}", err=True)
203
- return
204
-
205
- if enabled_only:
206
- jobs = [job for job in jobs if job.enabled]
207
-
208
- if not jobs:
209
- click.echo("No jobs found")
210
- return
211
-
212
- if output_format == "json":
213
- job_data = [job.to_dict() for job in jobs]
214
- click.echo(json.dumps(job_data, indent=2))
215
- else:
216
- # Table format
217
- from rich.console import Console
218
- from rich.table import Table
219
-
220
- console = Console()
221
- table = Table(show_header=True, header_style="bold magenta")
222
-
223
- table.add_column("Name", style="cyan")
224
- table.add_column("Schedule", style="green")
225
- table.add_column("Type", style="yellow")
226
- table.add_column("Status", style="red")
227
- table.add_column("Enabled", style="blue")
228
- table.add_column("Last Run", style="dim")
229
- table.add_column("Next Run", style="dim")
230
-
231
- for job in jobs:
232
- enabled_str = "✓" if job.enabled else "✗"
233
- last_run = job.last_run.strftime("%m-%d %H:%M") if job.last_run else "Never"
234
- next_run = job.next_run.strftime("%m-%d %H:%M") if job.next_run else "Unknown"
235
-
236
- table.add_row(
237
- job.name[:20],
238
- job.cron_expression[:15],
239
- job.job_type.value,
240
- job.status.value,
241
- enabled_str,
242
- last_run,
243
- next_run,
244
- )
245
-
246
- console.print(table)
247
-
248
-
249
- @scheduler.command()
250
- @click.argument("job_id")
251
- @click.option("--history", is_flag=True, help="Show execution history")
252
- @click.option("--verbose", "-v", is_flag=True, help="Show detailed information")
253
- def show(job_id: str, history: bool, verbose: bool):
254
- """Show detailed information about a job"""
255
- sched = get_scheduler()
256
-
257
- # Find job by ID or name
258
- job = sched.get_job(job_id)
259
- if not job:
260
- for j in sched.get_all_jobs():
261
- if j.name == job_id:
262
- job = j
263
- break
264
-
265
- if not job:
266
- click.echo(f"Job not found: {job_id}", err=True)
267
- return
268
-
269
- # Show basic info
270
- click.echo(f"Job: {job.name}")
271
- click.echo(f"ID: {job.id}")
272
- click.echo(f"Type: {job.job_type.value}")
273
- click.echo(f"Schedule: {job.cron_expression}")
274
- click.echo(f"Description: {job.description}")
275
- click.echo(f"Status: {job.status.value}")
276
- click.echo(f"Enabled: {'Yes' if job.enabled else 'No'}")
277
- click.echo(f"Created: {job.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
278
-
279
- if job.last_run:
280
- click.echo(f"Last Run: {job.last_run.strftime('%Y-%m-%d %H:%M:%S')}")
281
- if job.next_run:
282
- click.echo(f"Next Run: {job.next_run.strftime('%Y-%m-%d %H:%M:%S')}")
283
-
284
- click.echo(f"Run Count: {job.run_count}")
285
- click.echo(f"Success Count: {job.success_count}")
286
- click.echo(f"Failure Count: {job.failure_count}")
287
-
288
- if verbose:
289
- click.echo(f"\nCommand: {job.command}")
290
- click.echo(f"Max Runtime: {job.max_runtime}s")
291
- click.echo(f"Retry Count: {job.retry_count}")
292
- click.echo(f"Retry Delay: {job.retry_delay}s")
293
-
294
- if job.working_directory:
295
- click.echo(f"Working Directory: {job.working_directory}")
296
-
297
- if job.environment:
298
- click.echo("Environment Variables:")
299
- for key, value in job.environment.items():
300
- click.echo(f" {key}={value}")
301
-
302
- if job.last_output:
303
- click.echo(f"\nLast Output:\n{job.last_output}")
304
-
305
- if job.last_error:
306
- click.echo(f"\nLast Error:\n{job.last_error}")
307
-
308
- if history:
309
- click.echo(f"\nExecution History:")
310
- job_history = sched.storage.get_job_history(job.id, limit=10)
311
-
312
- if not job_history:
313
- click.echo(" No execution history")
314
- else:
315
- for record in job_history:
316
- executed_at = record.get("executed_at", "Unknown")
317
- status = record.get("status", "unknown")
318
- runtime = record.get("runtime_seconds", 0)
319
- click.echo(f" {executed_at}: {status} ({runtime}s)")
320
-
321
-
322
- @scheduler.command()
323
- @click.argument("job_id")
324
- @click.option("--enabled/--disabled", help="Enable or disable the job")
325
- @click.option("--cron", help="Update cron expression")
326
- @click.option("--command", help="Update command")
327
- @click.option("--description", help="Update description")
328
- @click.option("--max-runtime", type=int, help="Update max runtime")
329
- def update(
330
- job_id: str,
331
- enabled: Optional[bool],
332
- cron: Optional[str],
333
- command: Optional[str],
334
- description: Optional[str],
335
- max_runtime: Optional[int],
336
- ):
337
- """Update a scheduled job"""
338
- sched = get_scheduler()
339
-
340
- # Find job
341
- job = sched.get_job(job_id)
342
- if not job:
343
- for j in sched.get_all_jobs():
344
- if j.name == job_id:
345
- job = j
346
- break
347
-
348
- if not job:
349
- click.echo(f"Job not found: {job_id}", err=True)
350
- return
351
-
352
- # Update fields
353
- updated = False
354
- if enabled is not None:
355
- job.enabled = enabled
356
- updated = True
357
-
358
- if cron:
359
- if not validate_cron_expression(cron):
360
- click.echo(f"Error: Invalid cron expression: {cron}", err=True)
361
- return
362
- job.cron_expression = cron
363
- updated = True
364
-
365
- if command:
366
- job.command = command
367
- updated = True
368
-
369
- if description is not None:
370
- job.description = description
371
- updated = True
372
-
373
- if max_runtime:
374
- job.max_runtime = max_runtime
375
- updated = True
376
-
377
- if updated:
378
- sched.storage.save_job(job)
379
- click.echo(f"Updated job: {job.name}")
380
- else:
381
- click.echo("No changes made")
382
-
383
-
384
- @scheduler.command()
385
- @click.argument("cron_expression")
386
- @click.option("--count", default=5, help="Number of future run times to show")
387
- def test_cron(cron_expression: str, count: int):
388
- """Test a cron expression and show next run times"""
389
- if not validate_cron_expression(cron_expression):
390
- click.echo(f"Error: Invalid cron expression: {cron_expression}", err=True)
391
- return
392
-
393
- try:
394
- cron = CronExpression(cron_expression)
395
- click.echo(f"Cron Expression: {cron_expression}")
396
- click.echo(f"Description: {cron.get_description()}")
397
-
398
- if cron.is_reboot:
399
- click.echo("This is a @reboot job (runs only at scheduler startup)")
400
- else:
401
- next_times = get_next_run_times(cron_expression, count)
402
- if next_times:
403
- click.echo(f"\nNext {len(next_times)} run times:")
404
- for i, time in enumerate(next_times, 1):
405
- click.echo(f" {i}. {time.strftime('%Y-%m-%d %H:%M:%S %A')}")
406
- else:
407
- click.echo("Could not calculate next run times")
408
- except Exception as e:
409
- click.echo(f"Error: {e}", err=True)
410
-
411
-
412
- @scheduler.command()
413
- @click.option("--json", "output_json", is_flag=True, help="Output JSON for frontend")
414
- def status(output_json: bool):
415
- """Show scheduler status and statistics"""
416
- sched = get_scheduler()
417
- stats = sched.get_scheduler_stats()
418
-
419
- if output_json:
420
- response = sched.create_json_response()
421
- click.echo(json.dumps(response, indent=2))
422
- else:
423
- click.echo(f"Scheduler Status: {'Running' if stats['running'] else 'Stopped'}")
424
- click.echo(f"Total Jobs: {stats['total_jobs']}")
425
- click.echo(f"Enabled Jobs: {stats['enabled_jobs']}")
426
- click.echo(f"Currently Running: {stats['running_jobs']}")
427
-
428
- monitor_stats = stats.get("monitor_stats", {})
429
- if monitor_stats.get("running_job_ids"):
430
- click.echo(f"Running Job IDs: {', '.join(monitor_stats['running_job_ids'])}")
431
-
432
- storage_info = stats.get("storage_info", {})
433
- if storage_info:
434
- click.echo(f"Storage Directory: {storage_info.get('storage_dir', 'Unknown')}")
435
- click.echo(f"Jobs in Storage: {storage_info.get('jobs_count', 0)}")
436
- click.echo(f"History Records: {storage_info.get('history_count', 0)}")
437
-
438
-
439
- @scheduler.group()
440
- def presets():
441
- """Pre-configured job templates"""
442
- pass
443
-
444
-
445
- @presets.command()
446
- @click.option("--cron", default="0 9 * * 1", help="Cron expression (default: Monday 9 AM)")
447
- @click.option("--enabled/--disabled", default=True, help="Enable job")
448
- def desktop_cleanup(cron: str, enabled: bool):
449
- """Add desktop file organization job"""
450
- job = create_desktop_cleanup_job("Desktop Organization", cron, enabled)
451
-
452
- sched = get_scheduler()
453
- if sched.add_job(job):
454
- click.echo(f"Added desktop cleanup job: {job.name}")
455
- else:
456
- click.echo("Failed to add desktop cleanup job", err=True)
457
-
458
-
459
- @presets.command()
460
- @click.option("--path", default="/tmp", help="Path to clean up")
461
- @click.option("--days", default=7, help="Delete files older than this many days")
462
- @click.option("--cron", default="0 2 * * *", help="Cron expression (default: daily 2 AM)")
463
- @click.option("--enabled/--disabled", default=True, help="Enable job")
464
- def temp_cleanup(path: str, days: int, cron: str, enabled: bool):
465
- """Add temporary file cleanup job"""
466
- job = create_temp_cleanup_job("Temp File Cleanup", cron, path, days, enabled)
467
-
468
- sched = get_scheduler()
469
- if sched.add_job(job):
470
- click.echo(f"Added temp cleanup job: {job.name}")
471
- else:
472
- click.echo("Failed to add temp cleanup job", err=True)
473
-
474
-
475
- @presets.command()
476
- @click.argument("backup_command")
477
- @click.option("--cron", default="0 1 * * 0", help="Cron expression (default: Sunday 1 AM)")
478
- @click.option("--enabled/--disabled", default=True, help="Enable job")
479
- def system_backup(backup_command: str, cron: str, enabled: bool):
480
- """Add system backup job"""
481
- job = create_system_backup_job("System Backup", cron, backup_command, enabled)
482
-
483
- sched = get_scheduler()
484
- if sched.add_job(job):
485
- click.echo(f"Added system backup job: {job.name}")
486
- else:
487
- click.echo("Failed to add system backup job", err=True)
488
-
489
-
490
- # Add the scheduler commands to the main CLI
491
- def register_scheduler_commands(cli_group):
492
- """Register scheduler commands with the main CLI"""
493
- cli_group.add_command(scheduler)