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.
- mcli/__init__.py +160 -0
- mcli/__main__.py +14 -0
- mcli/app/__init__.py +23 -0
- mcli/app/commands_cmd.py +405 -58
- mcli/app/main.py +21 -27
- mcli/app/model/__init__.py +0 -0
- mcli/app/video/__init__.py +5 -0
- mcli/chat/__init__.py +34 -0
- mcli/lib/__init__.py +0 -0
- mcli/lib/api/__init__.py +0 -0
- mcli/lib/auth/__init__.py +1 -0
- mcli/lib/config/__init__.py +1 -0
- mcli/lib/custom_commands.py +52 -12
- mcli/lib/erd/__init__.py +25 -0
- mcli/lib/files/__init__.py +0 -0
- mcli/lib/fs/__init__.py +1 -0
- mcli/lib/logger/__init__.py +3 -0
- mcli/lib/paths.py +133 -5
- mcli/lib/performance/__init__.py +17 -0
- mcli/lib/pickles/__init__.py +1 -0
- mcli/lib/secrets/__init__.py +10 -0
- mcli/lib/shell/__init__.py +0 -0
- mcli/lib/toml/__init__.py +1 -0
- mcli/lib/watcher/__init__.py +0 -0
- mcli/ml/__init__.py +16 -0
- mcli/ml/api/__init__.py +30 -0
- mcli/ml/api/routers/__init__.py +27 -0
- mcli/ml/auth/__init__.py +41 -0
- mcli/ml/backtesting/__init__.py +33 -0
- mcli/ml/cli/__init__.py +5 -0
- mcli/ml/config/__init__.py +33 -0
- mcli/ml/configs/__init__.py +16 -0
- mcli/ml/dashboard/__init__.py +12 -0
- mcli/ml/dashboard/components/__init__.py +7 -0
- mcli/ml/dashboard/pages/__init__.py +6 -0
- mcli/ml/data_ingestion/__init__.py +29 -0
- mcli/ml/database/__init__.py +40 -0
- mcli/ml/experimentation/__init__.py +29 -0
- mcli/ml/features/__init__.py +39 -0
- mcli/ml/mlops/__init__.py +19 -0
- mcli/ml/models/__init__.py +90 -0
- mcli/ml/monitoring/__init__.py +25 -0
- mcli/ml/optimization/__init__.py +27 -0
- mcli/ml/predictions/__init__.py +5 -0
- mcli/ml/preprocessing/__init__.py +24 -0
- mcli/ml/scripts/__init__.py +1 -0
- mcli/ml/serving/__init__.py +1 -0
- mcli/ml/trading/__init__.py +63 -0
- mcli/ml/training/__init__.py +7 -0
- mcli/mygroup/__init__.py +3 -0
- mcli/public/__init__.py +1 -0
- mcli/public/commands/__init__.py +2 -0
- mcli/self/__init__.py +3 -0
- mcli/self/migrate_cmd.py +261 -0
- mcli/self/self_cmd.py +8 -0
- mcli/workflow/__init__.py +0 -0
- mcli/workflow/daemon/__init__.py +15 -0
- mcli/workflow/dashboard/__init__.py +5 -0
- mcli/workflow/docker/__init__.py +0 -0
- mcli/workflow/file/__init__.py +0 -0
- mcli/workflow/gcloud/__init__.py +1 -0
- mcli/workflow/git_commit/__init__.py +0 -0
- mcli/workflow/interview/__init__.py +0 -0
- mcli/workflow/notebook/__init__.py +16 -0
- mcli/workflow/registry/__init__.py +0 -0
- mcli/workflow/repo/__init__.py +0 -0
- mcli/workflow/scheduler/__init__.py +25 -0
- mcli/workflow/search/__init__.py +0 -0
- mcli/workflow/secrets/__init__.py +4 -0
- mcli/workflow/secrets/secrets_cmd.py +192 -0
- mcli/workflow/sync/__init__.py +5 -0
- mcli/workflow/videos/__init__.py +1 -0
- mcli/workflow/wakatime/__init__.py +80 -0
- mcli/workflow/workflow.py +22 -6
- {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/METADATA +69 -54
- {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/RECORD +80 -12
- {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/WHEEL +0 -0
- {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.10.2.dist-info → mcli_framework-7.11.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
177
|
-
"""Manage
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
295
|
-
"""Search commands
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
958
|
+
@workflow.command("add")
|
|
680
959
|
@click.argument("command_name", required=True)
|
|
681
|
-
@click.option("--group",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
842
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
|
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
|
-
@
|
|
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",
|
|
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
|
-
|
|
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
|
-
@
|
|
1146
|
-
|
|
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
|
-
@
|
|
1184
|
-
|
|
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
|
-
@
|
|
1514
|
+
@workflow.command("edit")
|
|
1199
1515
|
@click.argument("command_name")
|
|
1200
1516
|
@click.option("--editor", "-e", help="Editor to use (defaults to $EDITOR)")
|
|
1201
|
-
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
|
|
1470
|
-
"""Push
|
|
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
|
-
|
|
1525
|
-
"""Pull
|
|
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
|
-
|
|
1573
|
-
"""
|
|
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
|
-
@
|
|
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
|
)
|