mcli-framework 7.10.2__py3-none-any.whl → 7.11.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 (80) hide show
  1. mcli/__init__.py +160 -0
  2. mcli/__main__.py +14 -0
  3. mcli/app/__init__.py +23 -0
  4. mcli/app/commands_cmd.py +405 -58
  5. mcli/app/main.py +21 -27
  6. mcli/app/model/__init__.py +0 -0
  7. mcli/app/video/__init__.py +5 -0
  8. mcli/chat/__init__.py +34 -0
  9. mcli/lib/__init__.py +0 -0
  10. mcli/lib/api/__init__.py +0 -0
  11. mcli/lib/auth/__init__.py +1 -0
  12. mcli/lib/config/__init__.py +1 -0
  13. mcli/lib/custom_commands.py +52 -12
  14. mcli/lib/erd/__init__.py +25 -0
  15. mcli/lib/files/__init__.py +0 -0
  16. mcli/lib/fs/__init__.py +1 -0
  17. mcli/lib/logger/__init__.py +3 -0
  18. mcli/lib/paths.py +133 -5
  19. mcli/lib/performance/__init__.py +17 -0
  20. mcli/lib/pickles/__init__.py +1 -0
  21. mcli/lib/secrets/__init__.py +10 -0
  22. mcli/lib/shell/__init__.py +0 -0
  23. mcli/lib/toml/__init__.py +1 -0
  24. mcli/lib/watcher/__init__.py +0 -0
  25. mcli/ml/__init__.py +16 -0
  26. mcli/ml/api/__init__.py +30 -0
  27. mcli/ml/api/routers/__init__.py +27 -0
  28. mcli/ml/auth/__init__.py +41 -0
  29. mcli/ml/backtesting/__init__.py +33 -0
  30. mcli/ml/cli/__init__.py +5 -0
  31. mcli/ml/config/__init__.py +33 -0
  32. mcli/ml/configs/__init__.py +16 -0
  33. mcli/ml/dashboard/__init__.py +12 -0
  34. mcli/ml/dashboard/components/__init__.py +7 -0
  35. mcli/ml/dashboard/pages/__init__.py +6 -0
  36. mcli/ml/data_ingestion/__init__.py +29 -0
  37. mcli/ml/database/__init__.py +40 -0
  38. mcli/ml/experimentation/__init__.py +29 -0
  39. mcli/ml/features/__init__.py +39 -0
  40. mcli/ml/mlops/__init__.py +19 -0
  41. mcli/ml/models/__init__.py +90 -0
  42. mcli/ml/monitoring/__init__.py +25 -0
  43. mcli/ml/optimization/__init__.py +27 -0
  44. mcli/ml/predictions/__init__.py +5 -0
  45. mcli/ml/preprocessing/__init__.py +24 -0
  46. mcli/ml/scripts/__init__.py +1 -0
  47. mcli/ml/serving/__init__.py +1 -0
  48. mcli/ml/trading/__init__.py +63 -0
  49. mcli/ml/training/__init__.py +7 -0
  50. mcli/mygroup/__init__.py +3 -0
  51. mcli/public/__init__.py +1 -0
  52. mcli/public/commands/__init__.py +2 -0
  53. mcli/self/__init__.py +3 -0
  54. mcli/self/migrate_cmd.py +261 -0
  55. mcli/self/self_cmd.py +8 -0
  56. mcli/workflow/__init__.py +0 -0
  57. mcli/workflow/daemon/__init__.py +15 -0
  58. mcli/workflow/dashboard/__init__.py +5 -0
  59. mcli/workflow/docker/__init__.py +0 -0
  60. mcli/workflow/file/__init__.py +0 -0
  61. mcli/workflow/gcloud/__init__.py +1 -0
  62. mcli/workflow/git_commit/__init__.py +0 -0
  63. mcli/workflow/interview/__init__.py +0 -0
  64. mcli/workflow/notebook/__init__.py +16 -0
  65. mcli/workflow/registry/__init__.py +0 -0
  66. mcli/workflow/repo/__init__.py +0 -0
  67. mcli/workflow/scheduler/__init__.py +25 -0
  68. mcli/workflow/search/__init__.py +0 -0
  69. mcli/workflow/secrets/__init__.py +4 -0
  70. mcli/workflow/secrets/secrets_cmd.py +192 -0
  71. mcli/workflow/sync/__init__.py +5 -0
  72. mcli/workflow/videos/__init__.py +1 -0
  73. mcli/workflow/wakatime/__init__.py +80 -0
  74. mcli/workflow/workflow.py +22 -6
  75. {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/METADATA +69 -54
  76. {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/RECORD +80 -12
  77. {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/WHEEL +0 -0
  78. {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/entry_points.txt +0 -0
  79. {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/licenses/LICENSE +0 -0
  80. {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/top_level.txt +0 -0
mcli/app/commands_cmd.py CHANGED
@@ -172,38 +172,303 @@ def restore_command_state(hash_value):
172
172
  return True
173
173
 
174
174
 
175
- @click.group()
176
- def commands():
177
- """Manage and execute available commands."""
175
+ @click.group(name="workflow")
176
+ def workflow():
177
+ """Manage workflows - create, edit, import, export workflow commands."""
178
178
  pass
179
179
 
180
180
 
181
- @commands.command("list")
181
+ # For backward compatibility, keep commands as an alias
182
+ commands = workflow
183
+
184
+
185
+ @workflow.command("init")
186
+ @click.option(
187
+ "--global", "-g", "is_global", is_flag=True, help="Initialize global workflows directory instead of local"
188
+ )
189
+ @click.option(
190
+ "--git", is_flag=True, help="Initialize git repository in workflows directory"
191
+ )
192
+ @click.option(
193
+ "--force", "-f", is_flag=True, help="Force initialization even if directory exists"
194
+ )
195
+ def init_workflows(is_global, git, force):
196
+ """
197
+ Initialize workflows directory structure.
198
+
199
+ Creates the necessary directories and configuration files for managing
200
+ custom workflows. By default, creates a local .mcli/workflows/ directory
201
+ if in a git repository, otherwise uses ~/.mcli/workflows/.
202
+
203
+ Examples:
204
+ mcli workflow init # Initialize local workflows (if in git repo)
205
+ mcli workflow init --global # Initialize global workflows
206
+ mcli workflow init --git # Also initialize git repository
207
+ """
208
+ from mcli.lib.paths import (
209
+ get_custom_commands_dir,
210
+ get_lockfile_path,
211
+ get_git_root,
212
+ is_git_repository,
213
+ )
214
+
215
+ # Determine if we're in a git repository
216
+ in_git_repo = is_git_repository() and not is_global
217
+ git_root = get_git_root() if in_git_repo else None
218
+
219
+ # Get the workflows directory
220
+ workflows_dir = get_custom_commands_dir(global_mode=is_global)
221
+ lockfile_path = get_lockfile_path(global_mode=is_global)
222
+
223
+ # Check if already initialized
224
+ if workflows_dir.exists() and not force:
225
+ if lockfile_path.exists():
226
+ console.print(f"[yellow]Workflows directory already initialized at:[/yellow] {workflows_dir}")
227
+ console.print(f"[dim]Use --force to reinitialize[/dim]")
228
+
229
+ should_continue = Prompt.ask(
230
+ "Continue anyway?", choices=["y", "n"], default="n"
231
+ )
232
+ if should_continue.lower() != "y":
233
+ return 0
234
+
235
+ # Create workflows directory
236
+ workflows_dir.mkdir(parents=True, exist_ok=True)
237
+ console.print(f"[green]✓[/green] Created workflows directory: {workflows_dir}")
238
+
239
+ # Create README.md
240
+ readme_path = workflows_dir / "README.md"
241
+ if not readme_path.exists() or force:
242
+ scope = "local" if in_git_repo else "global"
243
+ scope_desc = f"for repository: {git_root.name}" if in_git_repo else "globally"
244
+
245
+ readme_content = f"""# MCLI Custom Workflows
246
+
247
+ This directory contains custom workflow commands {scope_desc}.
248
+
249
+ ## Quick Start
250
+
251
+ ### Create a New Workflow
252
+
253
+ ```bash
254
+ # Python workflow
255
+ mcli workflow add my-workflow
256
+
257
+ # Shell workflow
258
+ mcli workflow add my-script --language shell
259
+ ```
260
+
261
+ ### List Workflows
262
+
263
+ ```bash
264
+ mcli workflow list --custom-only
265
+ ```
266
+
267
+ ### Execute a Workflow
268
+
269
+ ```bash
270
+ mcli workflows my-workflow
271
+ ```
272
+
273
+ ### Edit a Workflow
274
+
275
+ ```bash
276
+ mcli workflow edit my-workflow
277
+ ```
278
+
279
+ ### Export/Import Workflows
280
+
281
+ ```bash
282
+ # Export all workflows
283
+ mcli workflow export workflows-backup.json
284
+
285
+ # Import workflows
286
+ mcli workflow import workflows-backup.json
287
+ ```
288
+
289
+ ## Directory Structure
290
+
291
+ ```
292
+ {workflows_dir.name}/
293
+ ├── README.md # This file
294
+ ├── commands.lock.json # Lockfile for workflow state
295
+ └── *.json # Individual workflow definitions
296
+ ```
297
+
298
+ ## Workflow Format
299
+
300
+ Workflows are stored as JSON files with the following structure:
301
+
302
+ ```json
303
+ {{
304
+ "name": "workflow-name",
305
+ "description": "Workflow description",
306
+ "code": "Python or shell code",
307
+ "language": "python",
308
+ "group": "workflow",
309
+ "version": "1.0",
310
+ "created_at": "2025-10-30T...",
311
+ "updated_at": "2025-10-30T..."
312
+ }}
313
+ ```
314
+
315
+ ## Scope
316
+
317
+ - **Scope**: {'Local (repository-specific)' if in_git_repo else 'Global (user-wide)'}
318
+ - **Location**: `{workflows_dir}`
319
+ {f"- **Git Repository**: `{git_root}`" if git_root else ""}
320
+
321
+ ## Documentation
322
+
323
+ - [MCLI Documentation](https://github.com/gwicho38/mcli)
324
+ - [Workflow Guide](https://github.com/gwicho38/mcli/blob/main/docs/features/LOCAL_VS_GLOBAL_COMMANDS.md)
325
+
326
+ ---
327
+
328
+ *Initialized: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*
329
+ """
330
+
331
+ readme_path.write_text(readme_content)
332
+ console.print(f"[green]✓[/green] Created README: {readme_path.name}")
333
+
334
+ # Initialize lockfile
335
+ if not lockfile_path.exists() or force:
336
+ lockfile_data = {
337
+ "version": "1.0",
338
+ "initialized_at": datetime.now().isoformat(),
339
+ "scope": "local" if in_git_repo else "global",
340
+ "commands": {}
341
+ }
342
+
343
+ with open(lockfile_path, "w") as f:
344
+ json.dump(lockfile_data, f, indent=2)
345
+
346
+ console.print(f"[green]✓[/green] Initialized lockfile: {lockfile_path.name}")
347
+
348
+ # Create .gitignore if in workflows directory
349
+ gitignore_path = workflows_dir / ".gitignore"
350
+ if not gitignore_path.exists() or force:
351
+ gitignore_content = """# Backup files
352
+ *.backup
353
+ *.bak
354
+
355
+ # Temporary files
356
+ *.tmp
357
+ *.temp
358
+
359
+ # OS files
360
+ .DS_Store
361
+ Thumbs.db
362
+
363
+ # Editor files
364
+ *.swp
365
+ *.swo
366
+ *~
367
+ .vscode/
368
+ .idea/
369
+ """
370
+ gitignore_path.write_text(gitignore_content)
371
+ console.print(f"[green]✓[/green] Created .gitignore")
372
+
373
+ # Initialize git if requested
374
+ if git and not (workflows_dir / ".git").exists():
375
+ try:
376
+ subprocess.run(
377
+ ["git", "init"],
378
+ cwd=workflows_dir,
379
+ check=True,
380
+ capture_output=True
381
+ )
382
+ console.print(f"[green]✓[/green] Initialized git repository in workflows directory")
383
+
384
+ # Create initial commit
385
+ subprocess.run(
386
+ ["git", "add", "."],
387
+ cwd=workflows_dir,
388
+ check=True,
389
+ capture_output=True
390
+ )
391
+ subprocess.run(
392
+ ["git", "commit", "-m", "Initial commit: Initialize workflows directory"],
393
+ cwd=workflows_dir,
394
+ check=True,
395
+ capture_output=True
396
+ )
397
+ console.print(f"[green]✓[/green] Created initial commit")
398
+
399
+ except subprocess.CalledProcessError as e:
400
+ console.print(f"[yellow]⚠[/yellow] Git initialization failed: {e}")
401
+ except FileNotFoundError:
402
+ console.print(f"[yellow]⚠[/yellow] Git not found. Skipping git initialization.")
403
+
404
+ # Summary
405
+ console.print()
406
+ console.print("[bold green]Workflows directory initialized successfully![/bold green]")
407
+ console.print()
408
+
409
+ # Display summary table
410
+ table = Table(title="Initialization Summary", show_header=False)
411
+ table.add_column("Property", style="cyan")
412
+ table.add_column("Value", style="green")
413
+
414
+ table.add_row("Scope", "Local (repository-specific)" if in_git_repo else "Global (user-wide)")
415
+ table.add_row("Location", str(workflows_dir))
416
+ if git_root:
417
+ table.add_row("Git Repository", str(git_root))
418
+ table.add_row("Lockfile", str(lockfile_path))
419
+ table.add_row("Git Initialized", "Yes" if git and (workflows_dir / ".git").exists() else "No")
420
+
421
+ console.print(table)
422
+ console.print()
423
+
424
+ # Next steps
425
+ console.print("[bold]Next Steps:[/bold]")
426
+ console.print(" 1. Create a workflow: [cyan]mcli workflow add my-workflow[/cyan]")
427
+ console.print(" 2. List workflows: [cyan]mcli workflow list --custom-only[/cyan]")
428
+ console.print(" 3. Execute workflow: [cyan]mcli workflows my-workflow[/cyan]")
429
+ console.print(" 4. View README: [cyan]cat {}/README.md[/cyan]".format(workflows_dir))
430
+ console.print()
431
+
432
+ if in_git_repo:
433
+ console.print(f"[dim]Tip: Workflows are local to this repository. Use --global for user-wide workflows.[/dim]")
434
+ else:
435
+ console.print(f"[dim]Tip: Use workflows in any git repository, or create local ones with 'mcli workflow init' inside repos.[/dim]")
436
+
437
+ return 0
438
+
439
+
440
+ @workflow.command("list")
182
441
  @click.option("--include-groups", is_flag=True, help="Include command groups in listing")
183
442
  @click.option("--daemon-only", is_flag=True, help="Show only daemon database commands")
184
443
  @click.option(
185
- "--custom-only", is_flag=True, help="Show only custom commands from ~/.mcli/commands/"
444
+ "--custom-only", is_flag=True, help="Show only custom commands from command directory"
186
445
  )
187
446
  @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
188
- def list_commands(include_groups: bool, daemon_only: bool, custom_only: bool, as_json: bool):
447
+ @click.option(
448
+ "--global", "-g", "is_global", is_flag=True, help="Use global commands (~/.mcli/commands/) instead of local (.mcli/commands/)"
449
+ )
450
+ def list_commands(include_groups: bool, daemon_only: bool, custom_only: bool, as_json: bool, is_global: bool):
189
451
  """
190
452
  List all available commands.
191
453
 
192
454
  By default, shows all discovered Click commands. Use flags to filter:
193
- - --custom-only: Show only custom commands from ~/.mcli/commands/
455
+ - --custom-only: Show only custom commands
194
456
  - --daemon-only: Show only daemon database commands
457
+ - --global/-g: Use global commands directory instead of local
195
458
 
196
459
  Examples:
197
- mcli commands list # Show all commands
198
- mcli commands list --custom-only # Show only custom commands
460
+ mcli commands list # Show all commands (local if in git repo)
461
+ mcli commands list --custom-only # Show only custom commands (local if in git repo)
462
+ mcli commands list --global # Show all global commands
463
+ mcli commands list --custom-only -g # Show only global custom commands
199
464
  mcli commands list --json # Output as JSON
200
465
  """
201
466
  from rich.table import Table
202
467
 
203
468
  try:
204
469
  if custom_only:
205
- # Show only custom commands from ~/.mcli/commands/
206
- manager = get_command_manager()
470
+ # Show only custom commands
471
+ manager = get_command_manager(global_mode=is_global)
207
472
  cmds = manager.load_all_commands()
208
473
 
209
474
  if not cmds:
@@ -232,7 +497,14 @@ def list_commands(include_groups: bool, daemon_only: bool, custom_only: bool, as
232
497
  )
233
498
 
234
499
  console.print(table)
235
- console.print(f"\n[dim]Commands directory: {manager.commands_dir}[/dim]")
500
+
501
+ # Show context information
502
+ scope = "global" if is_global or not manager.is_local else "local"
503
+ scope_color = "yellow" if scope == "local" else "cyan"
504
+ console.print(f"\n[dim]Scope: [{scope_color}]{scope}[/{scope_color}][/dim]")
505
+ if manager.is_local and manager.git_root:
506
+ console.print(f"[dim]Git repository: {manager.git_root}[/dim]")
507
+ console.print(f"[dim]Commands directory: {manager.commands_dir}[/dim]")
236
508
  console.print(f"[dim]Lockfile: {manager.lockfile_path}[/dim]")
237
509
 
238
510
  return 0
@@ -287,12 +559,19 @@ def list_commands(include_groups: bool, daemon_only: bool, custom_only: bool, as
287
559
  console.print(f"[red]Error: {e}[/red]")
288
560
 
289
561
 
290
- @commands.command("search")
562
+ @workflow.command("search")
291
563
  @click.argument("query")
292
564
  @click.option("--daemon-only", is_flag=True, help="Search only daemon database commands")
293
565
  @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
294
- def search_commands(query: str, daemon_only: bool, as_json: bool):
295
- """Search commands by name, description, or tags"""
566
+ @click.option(
567
+ "--global", "-g", "is_global", is_flag=True, help="Search global commands instead of local"
568
+ )
569
+ def search_commands(query: str, daemon_only: bool, as_json: bool, is_global: bool):
570
+ """
571
+ Search commands by name, description, or tags.
572
+
573
+ By default searches local commands (if in git repo), use --global/-g for global commands.
574
+ """
296
575
  try:
297
576
  if daemon_only:
298
577
  # Search only daemon database commands
@@ -358,7 +637,7 @@ def search_commands(query: str, daemon_only: bool, as_json: bool):
358
637
  console.print(f"[red]Error: {e}[/red]")
359
638
 
360
639
 
361
- @commands.command("execute")
640
+ @workflow.command("execute")
362
641
  @click.argument("command_name")
363
642
  @click.argument("args", nargs=-1)
364
643
  @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
@@ -388,7 +667,7 @@ def execute_command(command_name: str, args: tuple, as_json: bool, timeout: Opti
388
667
  console.print(f"[red]Error: {e}[/red]")
389
668
 
390
669
 
391
- @commands.command("info")
670
+ @workflow.command("info")
392
671
  @click.argument("command_name")
393
672
  @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
394
673
  def command_info(command_name: str, as_json: bool):
@@ -676,9 +955,9 @@ logger = get_logger()
676
955
  pass
677
956
 
678
957
 
679
- @commands.command("add")
958
+ @workflow.command("add")
680
959
  @click.argument("command_name", required=True)
681
- @click.option("--group", "-g", help="Command group (defaults to 'workflow')", default="workflow")
960
+ @click.option("--group", help="Command group (defaults to 'workflow')", default="workflow")
682
961
  @click.option("--description", "-d", help="Description for the command", default="Custom command")
683
962
  @click.option(
684
963
  "--template",
@@ -699,7 +978,10 @@ logger = get_logger()
699
978
  type=click.Choice(["bash", "zsh", "fish", "sh"], case_sensitive=False),
700
979
  help="Shell type for shell commands (defaults to $SHELL)",
701
980
  )
702
- def add_command(command_name, group, description, template, language, shell):
981
+ @click.option(
982
+ "--global", "-g", "is_global", is_flag=True, help="Add to global commands (~/.mcli/commands/) instead of local (.mcli/commands/)"
983
+ )
984
+ def add_command(command_name, group, description, template, language, shell, is_global):
703
985
  """
704
986
  Generate a new portable custom command saved to ~/.mcli/commands/.
705
987
 
@@ -749,7 +1031,7 @@ def add_command(command_name, group, description, template, language, shell):
749
1031
  command_group = "workflow" # Default to workflow group
750
1032
 
751
1033
  # Get the command manager
752
- manager = get_command_manager()
1034
+ manager = get_command_manager(global_mode=is_global)
753
1035
 
754
1036
  # Check if command already exists
755
1037
  command_file = manager.commands_dir / f"{command_name}.json"
@@ -830,29 +1112,45 @@ def add_command(command_name, group, description, template, language, shell):
830
1112
  )
831
1113
 
832
1114
  lang_display = f"{language}" if language == "python" else f"{language} ({shell})"
833
- logger.info(f"Created portable custom command: {command_name} ({lang_display})")
1115
+ scope = "global" if is_global or not manager.is_local else "local"
1116
+ scope_display = f"[cyan]{scope}[/cyan]" if scope == "global" else f"[yellow]{scope}[/yellow]"
1117
+
1118
+ logger.info(f"Created portable custom command: {command_name} ({lang_display}) [{scope}]")
834
1119
  console.print(
835
- f"[green]Created portable custom command: {command_name}[/green] [dim]({lang_display})[/dim]"
1120
+ f"[green]Created portable custom command: {command_name}[/green] [dim]({lang_display}) [Scope: {scope_display}][/dim]"
836
1121
  )
837
1122
  console.print(f"[dim]Saved to: {saved_path}[/dim]")
1123
+ if manager.is_local and manager.git_root:
1124
+ console.print(f"[dim]Git repository: {manager.git_root}[/dim]")
838
1125
  console.print(f"[dim]Group: {command_group}[/dim]")
839
1126
  console.print(f"[dim]Execute with: mcli {command_group} {command_name}[/dim]")
840
1127
  console.print("[dim]Command will be automatically loaded on next mcli startup[/dim]")
841
- console.print(
842
- f"[dim]You can share this command by copying {saved_path} to another machine's ~/.mcli/commands/ directory[/dim]"
843
- )
1128
+
1129
+ if scope == "global":
1130
+ console.print(
1131
+ f"[dim]You can share this command by copying {saved_path} to another machine's ~/.mcli/commands/ directory[/dim]"
1132
+ )
1133
+ else:
1134
+ console.print(
1135
+ f"[dim]This command is local to this git repository. Use --global/-g to create global commands.[/dim]"
1136
+ )
844
1137
 
845
1138
  return 0
846
1139
 
847
1140
 
848
- @commands.command("remove")
1141
+ @workflow.command("remove")
849
1142
  @click.argument("command_name", required=True)
850
1143
  @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
851
- def remove_command(command_name, yes):
1144
+ @click.option(
1145
+ "--global", "-g", "is_global", is_flag=True, help="Remove from global commands instead of local"
1146
+ )
1147
+ def remove_command(command_name, yes, is_global):
852
1148
  """
853
- Remove a custom command from ~/.mcli/commands/.
1149
+ Remove a custom command.
1150
+
1151
+ By default removes from local commands (if in git repo), use --global/-g for global commands.
854
1152
  """
855
- manager = get_command_manager()
1153
+ manager = get_command_manager(global_mode=is_global)
856
1154
  command_file = manager.commands_dir / f"{command_name}.json"
857
1155
 
858
1156
  if not command_file.exists():
@@ -875,7 +1173,7 @@ def remove_command(command_name, yes):
875
1173
  return 1
876
1174
 
877
1175
 
878
- @commands.command("export")
1176
+ @workflow.command("export")
879
1177
  @click.argument("target", type=click.Path(), required=False)
880
1178
  @click.option(
881
1179
  "--script", "-s", is_flag=True, help="Export as Python script (requires command name)"
@@ -884,7 +1182,10 @@ def remove_command(command_name, yes):
884
1182
  "--standalone", is_flag=True, help="Make script standalone (add if __name__ == '__main__')"
885
1183
  )
886
1184
  @click.option("--output", "-o", type=click.Path(), help="Output file path")
887
- def export_commands(target, script, standalone, output):
1185
+ @click.option(
1186
+ "--global", "-g", "is_global", is_flag=True, help="Export from global commands instead of local"
1187
+ )
1188
+ def export_commands(target, script, standalone, output, is_global):
888
1189
  """
889
1190
  Export custom commands to JSON file or export a single command to Python script.
890
1191
 
@@ -892,12 +1193,13 @@ def export_commands(target, script, standalone, output):
892
1193
  With --script/-s: Export a single command to Python script
893
1194
 
894
1195
  Examples:
895
- mcli commands export # Export all to commands-export.json
1196
+ mcli commands export # Export all local commands
1197
+ mcli commands export --global # Export all global commands
896
1198
  mcli commands export my-export.json # Export all to specified file
897
1199
  mcli commands export my-cmd -s # Export command to my-cmd.py
898
1200
  mcli commands export my-cmd -s -o out.py --standalone
899
1201
  """
900
- manager = get_command_manager()
1202
+ manager = get_command_manager(global_mode=is_global)
901
1203
 
902
1204
  if script:
903
1205
  # Export single command to Python script
@@ -972,12 +1274,12 @@ def export_commands(target, script, standalone, output):
972
1274
  return 1
973
1275
 
974
1276
 
975
- @commands.command("import")
1277
+ @workflow.command("import")
976
1278
  @click.argument("source", type=click.Path(exists=True), required=True)
977
1279
  @click.option("--script", "-s", is_flag=True, help="Import from Python script")
978
1280
  @click.option("--overwrite", is_flag=True, help="Overwrite existing commands")
979
1281
  @click.option("--name", "-n", help="Command name (for script import, defaults to script filename)")
980
- @click.option("--group", "-g", default="workflow", help="Command group (for script import)")
1282
+ @click.option("--group", default="workflow", help="Command group (for script import)")
981
1283
  @click.option("--description", "-d", help="Command description (for script import)")
982
1284
  @click.option(
983
1285
  "--interactive",
@@ -985,7 +1287,10 @@ def export_commands(target, script, standalone, output):
985
1287
  is_flag=True,
986
1288
  help="Open in $EDITOR for review/editing (for script import)",
987
1289
  )
988
- def import_commands(source, script, overwrite, name, group, description, interactive):
1290
+ @click.option(
1291
+ "--global", "-g", "is_global", is_flag=True, help="Import to global commands instead of local"
1292
+ )
1293
+ def import_commands(source, script, overwrite, name, group, description, interactive, is_global):
989
1294
  """
990
1295
  Import custom commands from JSON file or import a Python script as a command.
991
1296
 
@@ -993,13 +1298,14 @@ def import_commands(source, script, overwrite, name, group, description, interac
993
1298
  With --script/-s: Import a Python script as a command
994
1299
 
995
1300
  Examples:
996
- mcli commands import commands-export.json
1301
+ mcli commands import commands-export.json # Import to local (if in git repo)
1302
+ mcli commands import commands-export.json --global # Import to global
997
1303
  mcli commands import my_script.py -s
998
1304
  mcli commands import my_script.py -s --name custom-cmd --interactive
999
1305
  """
1000
1306
  import subprocess
1001
1307
 
1002
- manager = get_command_manager()
1308
+ manager = get_command_manager(global_mode=is_global)
1003
1309
  source_path = Path(source)
1004
1310
 
1005
1311
  if script:
@@ -1142,12 +1448,17 @@ def import_commands(source, script, overwrite, name, group, description, interac
1142
1448
  return 0
1143
1449
 
1144
1450
 
1145
- @commands.command("verify")
1146
- def verify_commands():
1451
+ @workflow.command("verify")
1452
+ @click.option(
1453
+ "--global", "-g", "is_global", is_flag=True, help="Verify global commands instead of local"
1454
+ )
1455
+ def verify_commands(is_global):
1147
1456
  """
1148
1457
  Verify that custom commands match the lockfile.
1458
+
1459
+ By default verifies local commands (if in git repo), use --global/-g for global commands.
1149
1460
  """
1150
- manager = get_command_manager()
1461
+ manager = get_command_manager(global_mode=is_global)
1151
1462
 
1152
1463
  # First, ensure lockfile is up to date
1153
1464
  manager.update_lockfile()
@@ -1180,12 +1491,17 @@ def verify_commands():
1180
1491
  return 1
1181
1492
 
1182
1493
 
1183
- @commands.command("update-lockfile")
1184
- def update_lockfile():
1494
+ @workflow.command("update-lockfile")
1495
+ @click.option(
1496
+ "--global", "-g", "is_global", is_flag=True, help="Update global lockfile instead of local"
1497
+ )
1498
+ def update_lockfile(is_global):
1185
1499
  """
1186
1500
  Update the commands lockfile with current state.
1501
+
1502
+ By default updates local lockfile (if in git repo), use --global/-g for global lockfile.
1187
1503
  """
1188
- manager = get_command_manager()
1504
+ manager = get_command_manager(global_mode=is_global)
1189
1505
 
1190
1506
  if manager.update_lockfile():
1191
1507
  console.print(f"[green]Updated lockfile: {manager.lockfile_path}[/green]")
@@ -1195,10 +1511,13 @@ def update_lockfile():
1195
1511
  return 1
1196
1512
 
1197
1513
 
1198
- @commands.command("edit")
1514
+ @workflow.command("edit")
1199
1515
  @click.argument("command_name")
1200
1516
  @click.option("--editor", "-e", help="Editor to use (defaults to $EDITOR)")
1201
- def edit_command(command_name, editor):
1517
+ @click.option(
1518
+ "--global", "-g", "is_global", is_flag=True, help="Edit global command instead of local"
1519
+ )
1520
+ def edit_command(command_name, editor, is_global):
1202
1521
  """
1203
1522
  Edit a command interactively using $EDITOR.
1204
1523
 
@@ -1206,12 +1525,13 @@ def edit_command(command_name, editor):
1206
1525
  allows you to make changes, and saves the updated version.
1207
1526
 
1208
1527
  Examples:
1209
- mcli commands edit my-command
1528
+ mcli commands edit my-command # Edit local command (if in git repo)
1529
+ mcli commands edit my-command --global # Edit global command
1210
1530
  mcli commands edit my-command --editor code
1211
1531
  """
1212
1532
  import subprocess
1213
1533
 
1214
- manager = get_command_manager()
1534
+ manager = get_command_manager(global_mode=is_global)
1215
1535
 
1216
1536
  # Load the command
1217
1537
  command_file = manager.commands_dir / f"{command_name}.json"
@@ -1294,7 +1614,7 @@ def edit_command(command_name, editor):
1294
1614
  # Moved from mcli.self for better organization
1295
1615
 
1296
1616
 
1297
- @commands.group("state")
1617
+ @workflow.group("state")
1298
1618
  def command_state():
1299
1619
  """Manage command state lockfile and history."""
1300
1620
  pass
@@ -1379,7 +1699,7 @@ def _get_store_path() -> Path:
1379
1699
  return DEFAULT_STORE_PATH
1380
1700
 
1381
1701
 
1382
- @commands.group("store")
1702
+ @workflow.group("store")
1383
1703
  def store():
1384
1704
  """Manage command store - sync ~/.mcli/commands/ to git"""
1385
1705
  pass
@@ -1466,10 +1786,19 @@ Last updated: {datetime.now().isoformat()}
1466
1786
  @store.command(name="push")
1467
1787
  @click.option("--message", "-m", help="Commit message")
1468
1788
  @click.option("--all", "-a", is_flag=True, help="Push all files (including backups)")
1469
- def push_commands(message, all):
1470
- """Push commands from ~/.mcli/commands/ to git store"""
1789
+ @click.option(
1790
+ "--global", "-g", "is_global", is_flag=True, help="Push global commands instead of local"
1791
+ )
1792
+ def push_commands(message, all, is_global):
1793
+ """
1794
+ Push commands to git store.
1795
+
1796
+ By default pushes local commands (if in git repo), use --global/-g for global commands.
1797
+ """
1471
1798
  try:
1472
1799
  store_path = _get_store_path()
1800
+ from mcli.lib.paths import get_custom_commands_dir
1801
+ COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
1473
1802
 
1474
1803
  # Copy commands to store
1475
1804
  info(f"Copying commands from {COMMANDS_PATH} to {store_path}...")
@@ -1521,10 +1850,19 @@ def push_commands(message, all):
1521
1850
 
1522
1851
  @store.command(name="pull")
1523
1852
  @click.option("--force", "-f", is_flag=True, help="Overwrite local commands without backup")
1524
- def pull_commands(force):
1525
- """Pull commands from git store to ~/.mcli/commands/"""
1853
+ @click.option(
1854
+ "--global", "-g", "is_global", is_flag=True, help="Pull to global commands instead of local"
1855
+ )
1856
+ def pull_commands(force, is_global):
1857
+ """
1858
+ Pull commands from git store.
1859
+
1860
+ By default pulls to local commands (if in git repo), use --global/-g for global commands.
1861
+ """
1526
1862
  try:
1527
1863
  store_path = _get_store_path()
1864
+ from mcli.lib.paths import get_custom_commands_dir
1865
+ COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
1528
1866
 
1529
1867
  # Pull from remote
1530
1868
  try:
@@ -1569,10 +1907,19 @@ def pull_commands(force):
1569
1907
 
1570
1908
  @store.command(name="sync")
1571
1909
  @click.option("--message", "-m", help="Commit message if pushing")
1572
- def sync_commands(message):
1573
- """Sync commands bidirectionally (pull then push if changes)"""
1910
+ @click.option(
1911
+ "--global", "-g", "is_global", is_flag=True, help="Sync global commands instead of local"
1912
+ )
1913
+ def sync_commands(message, is_global):
1914
+ """
1915
+ Sync commands bidirectionally (pull then push if changes).
1916
+
1917
+ By default syncs local commands (if in git repo), use --global/-g for global commands.
1918
+ """
1574
1919
  try:
1575
1920
  store_path = _get_store_path()
1921
+ from mcli.lib.paths import get_custom_commands_dir
1922
+ COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
1576
1923
 
1577
1924
  # First pull
1578
1925
  info("Pulling latest changes...")
@@ -1769,7 +2116,7 @@ def show_command(command_name, store):
1769
2116
  # Moved from mcli.self for better organization
1770
2117
 
1771
2118
 
1772
- @commands.command("extract-workflow-commands")
2119
+ @workflow.command("extract-workflow-commands")
1773
2120
  @click.option(
1774
2121
  "--output", "-o", type=click.Path(), help="Output file (default: workflow-commands.json)"
1775
2122
  )