mcli-framework 7.11.3__py3-none-any.whl → 7.12.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.
- mcli/app/commands_cmd.py +18 -823
- mcli/app/init_cmd.py +391 -0
- mcli/app/lock_cmd.py +288 -0
- mcli/app/main.py +37 -0
- mcli/app/store_cmd.py +448 -0
- mcli/lib/custom_commands.py +3 -3
- mcli/lib/optional_deps.py +1 -3
- mcli/self/migrate_cmd.py +209 -76
- mcli/workflow/secrets/__init__.py +1 -0
- mcli/workflow/secrets/secrets_cmd.py +1 -2
- mcli/workflow/workflow.py +39 -6
- {mcli_framework-7.11.3.dist-info → mcli_framework-7.12.0.dist-info}/METADATA +11 -11
- {mcli_framework-7.11.3.dist-info → mcli_framework-7.12.0.dist-info}/RECORD +17 -14
- {mcli_framework-7.11.3.dist-info → mcli_framework-7.12.0.dist-info}/WHEEL +0 -0
- {mcli_framework-7.11.3.dist-info → mcli_framework-7.12.0.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.11.3.dist-info → mcli_framework-7.12.0.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.11.3.dist-info → mcli_framework-7.12.0.dist-info}/top_level.txt +0 -0
mcli/self/migrate_cmd.py
CHANGED
|
@@ -32,72 +32,102 @@ def get_migration_status() -> dict:
|
|
|
32
32
|
Returns:
|
|
33
33
|
Dictionary with migration status information
|
|
34
34
|
"""
|
|
35
|
+
from mcli.lib.paths import get_git_root, is_git_repository
|
|
36
|
+
|
|
35
37
|
mcli_home = Path.home() / ".mcli"
|
|
36
38
|
old_commands_dir = mcli_home / "commands"
|
|
37
39
|
new_workflows_dir = mcli_home / "workflows"
|
|
38
40
|
|
|
39
41
|
status = {
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
"global": {
|
|
43
|
+
"old_dir_exists": old_commands_dir.exists(),
|
|
44
|
+
"old_dir_path": str(old_commands_dir),
|
|
45
|
+
"new_dir_exists": new_workflows_dir.exists(),
|
|
46
|
+
"new_dir_path": str(new_workflows_dir),
|
|
47
|
+
"needs_migration": False,
|
|
48
|
+
"files_to_migrate": [],
|
|
49
|
+
"migration_done": False,
|
|
50
|
+
},
|
|
51
|
+
"local": None,
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
# Check
|
|
54
|
+
# Check global migration
|
|
50
55
|
if old_commands_dir.exists():
|
|
51
56
|
# Count files that need migration (excluding hidden files)
|
|
52
57
|
files = [
|
|
53
|
-
f for f in old_commands_dir.iterdir()
|
|
54
|
-
if f.is_file() and not f.name.startswith('.')
|
|
58
|
+
f for f in old_commands_dir.iterdir() if f.is_file() and not f.name.startswith(".")
|
|
55
59
|
]
|
|
56
|
-
status["files_to_migrate"] = [f.name for f in files]
|
|
57
|
-
status["needs_migration"] = len(files) > 0
|
|
60
|
+
status["global"]["files_to_migrate"] = [f.name for f in files]
|
|
61
|
+
status["global"]["needs_migration"] = len(files) > 0
|
|
58
62
|
|
|
59
|
-
# Check if migration already done
|
|
63
|
+
# Check if global migration already done
|
|
60
64
|
if new_workflows_dir.exists() and not old_commands_dir.exists():
|
|
61
|
-
status["migration_done"] = True
|
|
65
|
+
status["global"]["migration_done"] = True
|
|
66
|
+
|
|
67
|
+
# Check local migration (if in git repo)
|
|
68
|
+
if is_git_repository():
|
|
69
|
+
git_root = get_git_root()
|
|
70
|
+
local_old = git_root / ".mcli" / "commands"
|
|
71
|
+
local_new = git_root / ".mcli" / "workflows"
|
|
72
|
+
|
|
73
|
+
status["local"] = {
|
|
74
|
+
"git_root": str(git_root),
|
|
75
|
+
"old_dir_exists": local_old.exists(),
|
|
76
|
+
"old_dir_path": str(local_old),
|
|
77
|
+
"new_dir_exists": local_new.exists(),
|
|
78
|
+
"new_dir_path": str(local_new),
|
|
79
|
+
"needs_migration": False,
|
|
80
|
+
"files_to_migrate": [],
|
|
81
|
+
"migration_done": False,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if local_old.exists():
|
|
85
|
+
files = [
|
|
86
|
+
f for f in local_old.iterdir() if f.is_file() and not f.name.startswith(".")
|
|
87
|
+
]
|
|
88
|
+
status["local"]["files_to_migrate"] = [f.name for f in files]
|
|
89
|
+
status["local"]["needs_migration"] = len(files) > 0
|
|
90
|
+
|
|
91
|
+
if local_new.exists() and not local_old.exists():
|
|
92
|
+
status["local"]["migration_done"] = True
|
|
62
93
|
|
|
63
94
|
return status
|
|
64
95
|
|
|
65
96
|
|
|
66
|
-
def
|
|
97
|
+
def migrate_directory(
|
|
98
|
+
old_dir: Path, new_dir: Path, dry_run: bool = False, force: bool = False
|
|
99
|
+
) -> Tuple[bool, str, List[str], List[str]]:
|
|
67
100
|
"""
|
|
68
|
-
Migrate
|
|
101
|
+
Migrate a commands directory to workflows directory.
|
|
69
102
|
|
|
70
103
|
Args:
|
|
104
|
+
old_dir: Source directory to migrate from
|
|
105
|
+
new_dir: Target directory to migrate to
|
|
71
106
|
dry_run: If True, show what would be done without actually doing it
|
|
72
107
|
force: If True, proceed even if workflows directory exists
|
|
73
108
|
|
|
74
109
|
Returns:
|
|
75
|
-
Tuple of (success, message)
|
|
110
|
+
Tuple of (success, message, migrated_files, skipped_files)
|
|
76
111
|
"""
|
|
77
|
-
mcli_home = Path.home() / ".mcli"
|
|
78
|
-
old_dir = mcli_home / "commands"
|
|
79
|
-
new_dir = mcli_home / "workflows"
|
|
80
|
-
|
|
81
112
|
# Check if old directory exists
|
|
82
113
|
if not old_dir.exists():
|
|
83
|
-
return False, f"Nothing to migrate: {old_dir} does not exist"
|
|
114
|
+
return False, f"Nothing to migrate: {old_dir} does not exist", [], []
|
|
84
115
|
|
|
85
116
|
# Check if new directory already exists
|
|
86
117
|
if new_dir.exists() and not force:
|
|
87
|
-
return False, f"Target directory {new_dir} already exists. Use --force to override."
|
|
118
|
+
return False, f"Target directory {new_dir} already exists. Use --force to override.", [], []
|
|
88
119
|
|
|
89
120
|
# Get list of files to migrate
|
|
90
|
-
files_to_migrate = [
|
|
91
|
-
f for f in old_dir.iterdir()
|
|
92
|
-
if f.is_file() and not f.name.startswith('.')
|
|
93
|
-
]
|
|
121
|
+
files_to_migrate = [f for f in old_dir.iterdir() if f.is_file() and not f.name.startswith(".")]
|
|
94
122
|
|
|
95
123
|
if not files_to_migrate:
|
|
96
|
-
return False, f"No files to migrate in {old_dir}"
|
|
124
|
+
return False, f"No files to migrate in {old_dir}", [], []
|
|
97
125
|
|
|
98
126
|
if dry_run:
|
|
99
|
-
message =
|
|
100
|
-
|
|
127
|
+
message = (
|
|
128
|
+
f"[DRY RUN] Would migrate {len(files_to_migrate)} files from {old_dir} to {new_dir}"
|
|
129
|
+
)
|
|
130
|
+
return True, message, [f.name for f in files_to_migrate], []
|
|
101
131
|
|
|
102
132
|
try:
|
|
103
133
|
# Create new directory if it doesn't exist
|
|
@@ -115,7 +145,9 @@ def migrate_commands_to_workflows(dry_run: bool = False, force: bool = False) ->
|
|
|
115
145
|
if target_path.exists():
|
|
116
146
|
if force:
|
|
117
147
|
# Backup existing file
|
|
118
|
-
backup_path = target_path.with_suffix(
|
|
148
|
+
backup_path = target_path.with_suffix(
|
|
149
|
+
f".backup.{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
|
150
|
+
)
|
|
119
151
|
shutil.move(str(target_path), str(backup_path))
|
|
120
152
|
logger.info(f"Backed up existing file to {backup_path}")
|
|
121
153
|
else:
|
|
@@ -129,8 +161,7 @@ def migrate_commands_to_workflows(dry_run: bool = False, force: bool = False) ->
|
|
|
129
161
|
|
|
130
162
|
# Check if old directory is now empty (only hidden files remain)
|
|
131
163
|
remaining_files = [
|
|
132
|
-
f for f in old_dir.iterdir()
|
|
133
|
-
if f.is_file() and not f.name.startswith('.')
|
|
164
|
+
f for f in old_dir.iterdir() if f.is_file() and not f.name.startswith(".")
|
|
134
165
|
]
|
|
135
166
|
|
|
136
167
|
# If empty, remove old directory
|
|
@@ -152,11 +183,62 @@ def migrate_commands_to_workflows(dry_run: bool = False, force: bool = False) ->
|
|
|
152
183
|
if remaining_files:
|
|
153
184
|
report_lines.append(f"Note: {len(remaining_files)} files remain in {old_dir}")
|
|
154
185
|
|
|
155
|
-
return True, "\n".join(report_lines)
|
|
186
|
+
return True, "\n".join(report_lines), migrated_files, skipped_files
|
|
156
187
|
|
|
157
188
|
except Exception as e:
|
|
158
189
|
logger.error(f"Migration failed: {e}")
|
|
159
|
-
return False, f"Migration failed: {str(e)}"
|
|
190
|
+
return False, f"Migration failed: {str(e)}", [], []
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def migrate_commands_to_workflows(
|
|
194
|
+
dry_run: bool = False, force: bool = False, scope: str = "all"
|
|
195
|
+
) -> Tuple[bool, str]:
|
|
196
|
+
"""
|
|
197
|
+
Migrate commands to workflows directories.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
dry_run: If True, show what would be done without actually doing it
|
|
201
|
+
force: If True, proceed even if workflows directory exists
|
|
202
|
+
scope: "global", "local", or "all" to control migration scope
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Tuple of (success, message)
|
|
206
|
+
"""
|
|
207
|
+
from mcli.lib.paths import get_git_root, is_git_repository
|
|
208
|
+
|
|
209
|
+
results = []
|
|
210
|
+
all_success = True
|
|
211
|
+
|
|
212
|
+
# Migrate global
|
|
213
|
+
if scope in ["global", "all"]:
|
|
214
|
+
mcli_home = Path.home() / ".mcli"
|
|
215
|
+
old_dir = mcli_home / "commands"
|
|
216
|
+
new_dir = mcli_home / "workflows"
|
|
217
|
+
|
|
218
|
+
success, message, migrated, skipped = migrate_directory(old_dir, new_dir, dry_run, force)
|
|
219
|
+
|
|
220
|
+
if old_dir.exists():
|
|
221
|
+
results.append(f"[Global] {message}")
|
|
222
|
+
if not success and "does not exist" not in message:
|
|
223
|
+
all_success = False
|
|
224
|
+
|
|
225
|
+
# Migrate local (if in git repo)
|
|
226
|
+
if scope in ["local", "all"] and is_git_repository():
|
|
227
|
+
git_root = get_git_root()
|
|
228
|
+
old_dir = git_root / ".mcli" / "commands"
|
|
229
|
+
new_dir = git_root / ".mcli" / "workflows"
|
|
230
|
+
|
|
231
|
+
success, message, migrated, skipped = migrate_directory(old_dir, new_dir, dry_run, force)
|
|
232
|
+
|
|
233
|
+
if old_dir.exists():
|
|
234
|
+
results.append(f"[Local - {git_root.name}] {message}")
|
|
235
|
+
if not success and "does not exist" not in message:
|
|
236
|
+
all_success = False
|
|
237
|
+
|
|
238
|
+
if not results:
|
|
239
|
+
return False, "No migrations needed"
|
|
240
|
+
|
|
241
|
+
return all_success, "\n".join(results)
|
|
160
242
|
|
|
161
243
|
|
|
162
244
|
@click.command(name="migrate", help="Perform system migrations for mcli")
|
|
@@ -175,18 +257,27 @@ def migrate_commands_to_workflows(dry_run: bool = False, force: bool = False) ->
|
|
|
175
257
|
is_flag=True,
|
|
176
258
|
help="Show migration status without performing migration",
|
|
177
259
|
)
|
|
178
|
-
|
|
260
|
+
@click.option(
|
|
261
|
+
"--scope",
|
|
262
|
+
type=click.Choice(["all", "global", "local"], case_sensitive=False),
|
|
263
|
+
default="all",
|
|
264
|
+
help="Migration scope: all (default), global (~/.mcli), or local (.mcli in current repo)",
|
|
265
|
+
)
|
|
266
|
+
def migrate_command(dry_run: bool, force: bool, status: bool, scope: str):
|
|
179
267
|
"""
|
|
180
268
|
Migrate mcli configuration and data to new structure.
|
|
181
269
|
|
|
182
270
|
Currently handles:
|
|
183
|
-
- Moving ~/.mcli/commands to ~/.mcli/workflows
|
|
271
|
+
- Moving ~/.mcli/commands to ~/.mcli/workflows (global)
|
|
272
|
+
- Moving .mcli/commands to .mcli/workflows (local, in git repos)
|
|
184
273
|
|
|
185
274
|
Examples:
|
|
186
|
-
mcli self migrate --status
|
|
187
|
-
mcli self migrate --dry-run
|
|
188
|
-
mcli self migrate
|
|
189
|
-
mcli self migrate --
|
|
275
|
+
mcli self migrate --status # Check migration status
|
|
276
|
+
mcli self migrate --dry-run # See what would be done
|
|
277
|
+
mcli self migrate # Perform migration (both global and local)
|
|
278
|
+
mcli self migrate --scope global # Migrate only global
|
|
279
|
+
mcli self migrate --scope local # Migrate only local (current repo)
|
|
280
|
+
mcli self migrate --force # Force migration (overwrite existing)
|
|
190
281
|
"""
|
|
191
282
|
|
|
192
283
|
# Get current status
|
|
@@ -195,53 +286,92 @@ def migrate_command(dry_run: bool, force: bool, status: bool):
|
|
|
195
286
|
# If --status flag, just show status and exit
|
|
196
287
|
if status:
|
|
197
288
|
console.print("\n[bold cyan]Migration Status[/bold cyan]")
|
|
198
|
-
console.print(f"\n[bold]Old location:[/bold] {migration_status['old_dir_path']}")
|
|
199
|
-
console.print(f" Exists: {'✓ Yes' if migration_status['old_dir_exists'] else '✗ No'}")
|
|
200
|
-
|
|
201
|
-
console.print(f"\n[bold]New location:[/bold] {migration_status['new_dir_path']}")
|
|
202
|
-
console.print(f" Exists: {'✓ Yes' if migration_status['new_dir_exists'] else '✗ No'}")
|
|
203
|
-
|
|
204
|
-
if migration_status['needs_migration']:
|
|
205
|
-
console.print(f"\n[yellow]⚠ Migration needed[/yellow]")
|
|
206
|
-
console.print(f"Files to migrate: {len(migration_status['files_to_migrate'])}")
|
|
207
289
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
console.print(f"
|
|
218
|
-
|
|
219
|
-
|
|
290
|
+
# Show global status
|
|
291
|
+
global_status = migration_status["global"]
|
|
292
|
+
console.print(f"\n[bold]Global (~/.mcli)[/bold]")
|
|
293
|
+
console.print(f" Old location: {global_status['old_dir_path']}")
|
|
294
|
+
console.print(f" Exists: {'✓ Yes' if global_status['old_dir_exists'] else '✗ No'}")
|
|
295
|
+
console.print(f" New location: {global_status['new_dir_path']}")
|
|
296
|
+
console.print(f" Exists: {'✓ Yes' if global_status['new_dir_exists'] else '✗ No'}")
|
|
297
|
+
|
|
298
|
+
if global_status["needs_migration"]:
|
|
299
|
+
console.print(f" [yellow]⚠ Migration needed[/yellow]")
|
|
300
|
+
console.print(f" Files to migrate: {len(global_status['files_to_migrate'])}")
|
|
301
|
+
elif global_status["migration_done"]:
|
|
302
|
+
console.print(f" [green]✓ Migration completed[/green]")
|
|
220
303
|
else:
|
|
221
|
-
console.print(f"
|
|
304
|
+
console.print(f" [green]✓ No migration needed[/green]")
|
|
305
|
+
|
|
306
|
+
# Show local status if in git repo
|
|
307
|
+
if migration_status["local"]:
|
|
308
|
+
local_status = migration_status["local"]
|
|
309
|
+
console.print(f"\n[bold]Local (current repository: {local_status['git_root']})[/bold]")
|
|
310
|
+
console.print(f" Old location: {local_status['old_dir_path']}")
|
|
311
|
+
console.print(f" Exists: {'✓ Yes' if local_status['old_dir_exists'] else '✗ No'}")
|
|
312
|
+
console.print(f" New location: {local_status['new_dir_path']}")
|
|
313
|
+
console.print(f" Exists: {'✓ Yes' if local_status['new_dir_exists'] else '✗ No'}")
|
|
314
|
+
|
|
315
|
+
if local_status["needs_migration"]:
|
|
316
|
+
console.print(f" [yellow]⚠ Migration needed[/yellow]")
|
|
317
|
+
console.print(f" Files to migrate: {len(local_status['files_to_migrate'])}")
|
|
318
|
+
elif local_status["migration_done"]:
|
|
319
|
+
console.print(f" [green]✓ Migration completed[/green]")
|
|
320
|
+
else:
|
|
321
|
+
console.print(f" [green]✓ No migration needed[/green]")
|
|
322
|
+
|
|
323
|
+
# Show files to migrate if any
|
|
324
|
+
all_files = global_status.get("files_to_migrate", [])
|
|
325
|
+
if migration_status["local"]:
|
|
326
|
+
all_files.extend(migration_status["local"].get("files_to_migrate", []))
|
|
327
|
+
|
|
328
|
+
if all_files:
|
|
329
|
+
console.print(f"\n[bold]Files to migrate:[/bold]")
|
|
330
|
+
table = Table()
|
|
331
|
+
table.add_column("Location", style="cyan")
|
|
332
|
+
table.add_column("File Name", style="yellow")
|
|
333
|
+
|
|
334
|
+
for filename in sorted(global_status.get("files_to_migrate", [])):
|
|
335
|
+
table.add_row("Global", filename)
|
|
336
|
+
|
|
337
|
+
if migration_status["local"]:
|
|
338
|
+
for filename in sorted(migration_status["local"].get("files_to_migrate", [])):
|
|
339
|
+
table.add_row("Local", filename)
|
|
340
|
+
|
|
341
|
+
console.print(table)
|
|
342
|
+
console.print(f"\n[dim]Run 'mcli self migrate' to perform migration[/dim]")
|
|
222
343
|
|
|
223
344
|
return
|
|
224
345
|
|
|
225
346
|
# Check if migration is needed
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
347
|
+
needs_any_migration = global_status["needs_migration"]
|
|
348
|
+
if migration_status["local"]:
|
|
349
|
+
needs_any_migration = needs_any_migration or migration_status["local"]["needs_migration"]
|
|
350
|
+
|
|
351
|
+
if not needs_any_migration:
|
|
352
|
+
info("No migration needed")
|
|
232
353
|
return
|
|
233
354
|
|
|
234
355
|
# Show what will be migrated
|
|
235
356
|
console.print("\n[bold cyan]Migration Plan[/bold cyan]")
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
357
|
+
|
|
358
|
+
if scope in ["global", "all"] and global_status["needs_migration"]:
|
|
359
|
+
console.print(f"\n[bold]Global:[/bold]")
|
|
360
|
+
console.print(f" Source: [cyan]{global_status['old_dir_path']}[/cyan]")
|
|
361
|
+
console.print(f" Target: [cyan]{global_status['new_dir_path']}[/cyan]")
|
|
362
|
+
console.print(f" Files: [yellow]{len(global_status['files_to_migrate'])}[/yellow]")
|
|
363
|
+
|
|
364
|
+
if scope in ["local", "all"] and migration_status["local"] and migration_status["local"]["needs_migration"]:
|
|
365
|
+
console.print(f"\n[bold]Local:[/bold]")
|
|
366
|
+
console.print(f" Source: [cyan]{migration_status['local']['old_dir_path']}[/cyan]")
|
|
367
|
+
console.print(f" Target: [cyan]{migration_status['local']['new_dir_path']}[/cyan]")
|
|
368
|
+
console.print(f" Files: [yellow]{len(migration_status['local']['files_to_migrate'])}[/yellow]")
|
|
239
369
|
|
|
240
370
|
if dry_run:
|
|
241
371
|
console.print(f"\n[yellow]DRY RUN MODE - No changes will be made[/yellow]")
|
|
242
372
|
|
|
243
373
|
# Perform migration
|
|
244
|
-
success_flag, message = migrate_commands_to_workflows(dry_run=dry_run, force=force)
|
|
374
|
+
success_flag, message = migrate_commands_to_workflows(dry_run=dry_run, force=force, scope=scope)
|
|
245
375
|
|
|
246
376
|
if success_flag:
|
|
247
377
|
if dry_run:
|
|
@@ -249,12 +379,15 @@ def migrate_command(dry_run: bool, force: bool, status: bool):
|
|
|
249
379
|
else:
|
|
250
380
|
success(message)
|
|
251
381
|
console.print("\n[green]✓ Migration completed successfully[/green]")
|
|
252
|
-
console.print(
|
|
253
|
-
|
|
382
|
+
console.print(
|
|
383
|
+
"\n[dim]You can now use 'mcli workflow' to manage and 'mcli workflows' to run them[/dim]"
|
|
384
|
+
)
|
|
254
385
|
else:
|
|
255
386
|
error(message)
|
|
256
387
|
if not force and "already exists" in message:
|
|
257
|
-
console.print(
|
|
388
|
+
console.print(
|
|
389
|
+
"\n[yellow]Tip: Use --force to proceed anyway (will backup existing files)[/yellow]"
|
|
390
|
+
)
|
|
258
391
|
|
|
259
392
|
|
|
260
393
|
if __name__ == "__main__":
|
|
@@ -10,12 +10,11 @@ from typing import Optional
|
|
|
10
10
|
|
|
11
11
|
import click
|
|
12
12
|
|
|
13
|
-
from mcli.lib.ui.styling import error, info, success, warning
|
|
14
|
-
|
|
15
13
|
# Import from the original lib.secrets modules (keeping the implementation)
|
|
16
14
|
from mcli.lib.secrets.manager import SecretsManager
|
|
17
15
|
from mcli.lib.secrets.repl import run_repl
|
|
18
16
|
from mcli.lib.secrets.store import SecretsStore
|
|
17
|
+
from mcli.lib.ui.styling import error, info, success, warning
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
@click.command(name="secrets", help="Secure secrets management with encryption and git sync")
|
mcli/workflow/workflow.py
CHANGED
|
@@ -17,16 +17,47 @@ class ScopedWorkflowsGroup(click.Group):
|
|
|
17
17
|
def list_commands(self, ctx):
|
|
18
18
|
"""List available commands based on scope."""
|
|
19
19
|
# Get scope from context
|
|
20
|
-
is_global = ctx.params.get(
|
|
20
|
+
is_global = ctx.params.get("is_global", False)
|
|
21
21
|
|
|
22
22
|
# Load commands from appropriate directory
|
|
23
23
|
from mcli.lib.custom_commands import get_command_manager
|
|
24
|
+
from mcli.lib.logger.logger import get_logger
|
|
24
25
|
|
|
26
|
+
logger = get_logger()
|
|
25
27
|
manager = get_command_manager(global_mode=is_global)
|
|
26
28
|
commands = manager.load_all_commands()
|
|
27
29
|
|
|
28
|
-
# Filter to only workflow group commands
|
|
29
|
-
workflow_commands = [
|
|
30
|
+
# Filter to only workflow group commands AND validate they can be loaded
|
|
31
|
+
workflow_commands = []
|
|
32
|
+
for cmd_data in commands:
|
|
33
|
+
if cmd_data.get("group") != "workflow":
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
cmd_name = cmd_data.get("name")
|
|
37
|
+
# Validate the command can be loaded
|
|
38
|
+
temp_group = click.Group()
|
|
39
|
+
language = cmd_data.get("language", "python")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
if language == "shell":
|
|
43
|
+
success = manager.register_shell_command_with_click(cmd_data, temp_group)
|
|
44
|
+
else:
|
|
45
|
+
success = manager.register_command_with_click(cmd_data, temp_group)
|
|
46
|
+
|
|
47
|
+
if success and temp_group.commands.get(cmd_name):
|
|
48
|
+
workflow_commands.append(cmd_name)
|
|
49
|
+
else:
|
|
50
|
+
# Log the issue but don't show to user during list (too noisy)
|
|
51
|
+
logger.debug(
|
|
52
|
+
f"Workflow '{cmd_name}' has invalid code and will not be loaded. "
|
|
53
|
+
f"Edit with: mcli workflow edit {cmd_name}"
|
|
54
|
+
)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
# Log the issue but don't show to user during list (too noisy)
|
|
57
|
+
logger.debug(
|
|
58
|
+
f"Failed to load workflow '{cmd_name}': {e}. "
|
|
59
|
+
f"Edit with: mcli workflow edit {cmd_name}"
|
|
60
|
+
)
|
|
30
61
|
|
|
31
62
|
# Also include built-in subcommands
|
|
32
63
|
builtin_commands = list(super().list_commands(ctx))
|
|
@@ -41,7 +72,7 @@ class ScopedWorkflowsGroup(click.Group):
|
|
|
41
72
|
return builtin_cmd
|
|
42
73
|
|
|
43
74
|
# Get scope from context
|
|
44
|
-
is_global = ctx.params.get(
|
|
75
|
+
is_global = ctx.params.get("is_global", False)
|
|
45
76
|
|
|
46
77
|
# Load the workflow command from appropriate directory
|
|
47
78
|
from mcli.lib.custom_commands import get_command_manager
|
|
@@ -51,7 +82,7 @@ class ScopedWorkflowsGroup(click.Group):
|
|
|
51
82
|
|
|
52
83
|
# Find the workflow command
|
|
53
84
|
for command_data in commands:
|
|
54
|
-
if command_data.get(
|
|
85
|
+
if command_data.get("name") == cmd_name and command_data.get("group") == "workflow":
|
|
55
86
|
# Create a temporary group to register the command
|
|
56
87
|
temp_group = click.Group()
|
|
57
88
|
language = command_data.get("language", "python")
|
|
@@ -85,7 +116,7 @@ def workflows(ctx, is_global):
|
|
|
85
116
|
"""
|
|
86
117
|
# Store the is_global flag in the context for subcommands to access
|
|
87
118
|
ctx.ensure_object(dict)
|
|
88
|
-
ctx.obj[
|
|
119
|
+
ctx.obj["is_global"] = is_global
|
|
89
120
|
|
|
90
121
|
# If a subcommand was invoked, the subcommand will handle execution
|
|
91
122
|
if ctx.invoked_subcommand:
|
|
@@ -103,6 +134,7 @@ try:
|
|
|
103
134
|
except ImportError as e:
|
|
104
135
|
# Secrets workflow not available
|
|
105
136
|
import sys
|
|
137
|
+
|
|
106
138
|
from mcli.lib.logger.logger import get_logger
|
|
107
139
|
|
|
108
140
|
logger = get_logger()
|
|
@@ -116,6 +148,7 @@ try:
|
|
|
116
148
|
except ImportError as e:
|
|
117
149
|
# Notebook commands not available
|
|
118
150
|
import sys
|
|
151
|
+
|
|
119
152
|
from mcli.lib.logger.logger import get_logger
|
|
120
153
|
|
|
121
154
|
logger = get_logger()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcli-framework
|
|
3
|
-
Version: 7.
|
|
4
|
-
Summary: Portable workflow framework - transform any script into a versioned, schedulable command. Store in ~/.mcli/
|
|
3
|
+
Version: 7.12.0
|
|
4
|
+
Summary: Portable workflow framework - transform any script into a versioned, schedulable command. Store in ~/.mcli/workflows/, version with lockfile, run as daemon or cron job.
|
|
5
5
|
Author-email: Luis Fernandez de la Vara <luis@lefv.io>
|
|
6
6
|
Maintainer-email: Luis Fernandez de la Vara <luis@lefv.io>
|
|
7
7
|
License: MIT
|
|
@@ -167,7 +167,7 @@ Dynamic: license-file
|
|
|
167
167
|
|
|
168
168
|
**Transform any script into a versioned, portable, schedulable workflow command.**
|
|
169
169
|
|
|
170
|
-
MCLI is a modular CLI framework that lets you write scripts once and run them anywhere - as interactive commands, scheduled jobs, or background daemons. Your workflows live in `~/.mcli/
|
|
170
|
+
MCLI is a modular CLI framework that lets you write scripts once and run them anywhere - as interactive commands, scheduled jobs, or background daemons. Your workflows live in `~/.mcli/workflows/`, are versioned via lockfile, and completely decoupled from the engine source code.
|
|
171
171
|
|
|
172
172
|
## 🎯 Core Philosophy
|
|
173
173
|
|
|
@@ -195,7 +195,7 @@ Edit your workflow JSON files like Jupyter notebooks with our VSCode extension!
|
|
|
195
195
|
code --install-extension gwicho38.mcli-framework
|
|
196
196
|
|
|
197
197
|
# Or install from VSIX
|
|
198
|
-
code --install-extension vscode-extension/mcli-framework-
|
|
198
|
+
code --install-extension vscode-extension/mcli-framework-2.0.0.vsix
|
|
199
199
|
```
|
|
200
200
|
|
|
201
201
|
**Learn More:**
|
|
@@ -220,13 +220,13 @@ uv pip install mcli-framework
|
|
|
220
220
|
|
|
221
221
|
```bash
|
|
222
222
|
# Initialize workflows in current git repository
|
|
223
|
-
mcli
|
|
223
|
+
mcli init
|
|
224
224
|
|
|
225
225
|
# Or initialize global workflows
|
|
226
|
-
mcli
|
|
226
|
+
mcli init --global
|
|
227
227
|
|
|
228
228
|
# Initialize with git repository for workflows
|
|
229
|
-
mcli
|
|
229
|
+
mcli init --git
|
|
230
230
|
```
|
|
231
231
|
|
|
232
232
|
This creates a `.mcli/workflows/` directory (local to your repo) or `~/.mcli/workflows/` (global) with:
|
|
@@ -314,10 +314,10 @@ mcli workflow import my-workflows.json
|
|
|
314
314
|
mcli workflow export-script my-workflow --output my_workflow.py
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
Your workflows are just JSON files in `~/.mcli/
|
|
317
|
+
Your workflows are just JSON files in `~/.mcli/workflows/`:
|
|
318
318
|
|
|
319
319
|
```bash
|
|
320
|
-
$ ls ~/.mcli/
|
|
320
|
+
$ ls ~/.mcli/workflows/
|
|
321
321
|
pdf-processor.json
|
|
322
322
|
data-sync.json
|
|
323
323
|
git-commit.json
|
|
@@ -358,7 +358,7 @@ Example `commands.lock.json`:
|
|
|
358
358
|
|
|
359
359
|
```bash
|
|
360
360
|
# Add lockfile to git
|
|
361
|
-
git add ~/.mcli/
|
|
361
|
+
git add ~/.mcli/workflows/commands.lock.json ~/.mcli/workflows/*.json
|
|
362
362
|
git commit -m "Update workflows"
|
|
363
363
|
|
|
364
364
|
# On another machine
|
|
@@ -513,7 +513,7 @@ You write scripts. They work. Then:
|
|
|
513
513
|
|
|
514
514
|
### The MCLI Solution
|
|
515
515
|
|
|
516
|
-
- ✅ **Centralized Storage**: All workflows in `~/.mcli/
|
|
516
|
+
- ✅ **Centralized Storage**: All workflows in `~/.mcli/workflows/`
|
|
517
517
|
- ✅ **Portable**: Export/import as JSON, share anywhere
|
|
518
518
|
- ✅ **Versioned**: Lockfile for reproducibility
|
|
519
519
|
- ✅ **Decoupled**: Zero coupling to engine source code
|
|
@@ -3,10 +3,13 @@ mcli/__main__.py,sha256=nKdf3WqtXi5PhWhZGjpXKAT3a2yGUYkYCBgSLxk4hSQ,295
|
|
|
3
3
|
mcli/cli.py,sha256=6KTyXn-pmVkAbCDu59PbiNKBwNra5su31ujFFZ6CBOM,389
|
|
4
4
|
mcli/config.toml,sha256=263yEVvP_W9F2zOLssUBgy7amKaRAFQuBrfxcMhKxaQ,1706
|
|
5
5
|
mcli/app/__init__.py,sha256=D4RiKk2gOEXwanbe_jXyNSb5zdgNi47kahtskMnEwjY,489
|
|
6
|
-
mcli/app/commands_cmd.py,sha256=
|
|
6
|
+
mcli/app/commands_cmd.py,sha256=RAwkNQIy2XFUQxn56M6rIKas7Qb9BhIFWB4iOj4zTFA,50255
|
|
7
7
|
mcli/app/completion_helpers.py,sha256=e62C6w2N-XoD66GYYHgtvKKoD3kYMuIeBBGzVKbuL04,7497
|
|
8
|
-
mcli/app/
|
|
8
|
+
mcli/app/init_cmd.py,sha256=Pueg96Gi10VJB20xdEwfw_tDj4v5A5f_dEzKj_130UU,12451
|
|
9
|
+
mcli/app/lock_cmd.py,sha256=dlMk94Lw7FtKDZC92H4GSOBKJva2sKWExIafIr1ftcQ,10146
|
|
10
|
+
mcli/app/main.py,sha256=wFfvILpaP9JmGvRaLqHoLYIAiUGhvPBzh2L149rn9zo,20561
|
|
9
11
|
mcli/app/model_cmd.py,sha256=LQQD8FaebFoaJGK3u_kt19wZ3HJyo_ecwSMYyC2xIp8,2497
|
|
12
|
+
mcli/app/store_cmd.py,sha256=Pr2S0G-TEC1UuGSIzyNnJBxPEc1lH42Cs8WCUsZHJ8I,14316
|
|
10
13
|
mcli/app/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
14
|
mcli/app/model/model.py,sha256=EUGu_td-hRlbf4OElkdk1-0p7WyuG7sZmb-Ux2-J9KY,39061
|
|
12
15
|
mcli/app/video/__init__.py,sha256=PxCZQrtClwNQr1RaF8Q3iXlLeQlnEAI9NO8LGcQQuvk,66
|
|
@@ -18,9 +21,9 @@ mcli/chat/enhanced_chat.py,sha256=e3odh5klewDHIjfNOyvifLzCdHrysDc2IvNVHzTPIh4,27
|
|
|
18
21
|
mcli/chat/system_controller.py,sha256=SuGvnIh2QObvM1DMicF3gGyeBkbz_xXS-hOOHjWx5j4,39114
|
|
19
22
|
mcli/chat/system_integration.py,sha256=xQ11thOUswPg8r1HZkId6U3bTCOtMYngt0-mUYYXpt4,40196
|
|
20
23
|
mcli/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
mcli/lib/custom_commands.py,sha256=
|
|
24
|
+
mcli/lib/custom_commands.py,sha256=y_S5Ys58EM2pEBcQjmGZy55Ff8O6ahFzuzNkmtMRUjo,21739
|
|
22
25
|
mcli/lib/lib.py,sha256=-CFUfmcubYBxt3LDBY0uj9DF232pz8MPDu-Qg0Ocy8M,850
|
|
23
|
-
mcli/lib/optional_deps.py,sha256=
|
|
26
|
+
mcli/lib/optional_deps.py,sha256=Yq5gGu3EB_Ahl68Xw2mtGryIvjf0CoSm6t7Ri-A7NJ0,7554
|
|
24
27
|
mcli/lib/paths.py,sha256=3-yseYbqQ-711GUSO8qlI3Tnlwx4BFO48IeiUETIY-A,6412
|
|
25
28
|
mcli/lib/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
29
|
mcli/lib/api/api.py,sha256=sPgAIYC8Z7AWV2TCBssNSKotbRggBqNLsbfzbjkhmUY,18558
|
|
@@ -199,7 +202,7 @@ mcli/public/oi/oi.py,sha256=SQabQWQ1pE67pWYEHwIDc3R93DARJfB6VHk7qxWx9xo,308
|
|
|
199
202
|
mcli/self/__init__.py,sha256=7hCrgaRb4oAgAf-kcyzqhJ5LFpW19jwF5bxowR4LwjY,41
|
|
200
203
|
mcli/self/completion_cmd.py,sha256=JXJ70p_yyfC8KlC5QifkQWLNeS2hgUFmNw8xdiix5O8,7764
|
|
201
204
|
mcli/self/logs_cmd.py,sha256=SCzZ4VZs6p42hksun_w4WN33xIZgmq7RjdWX8P2WcT4,15056
|
|
202
|
-
mcli/self/migrate_cmd.py,sha256=
|
|
205
|
+
mcli/self/migrate_cmd.py,sha256=DalSn-pvfez4KdYYaYlYi1hqqtZM6bgS--pNZzfVsE8,14665
|
|
203
206
|
mcli/self/redis_cmd.py,sha256=Cl0LQ3Mqt27gLeb542_xw6bJBbIE-CBmWyMmaUTSk8c,9426
|
|
204
207
|
mcli/self/self_cmd.py,sha256=m0GhY8u2o5Xo0ws9QaTO_UbdwloOtNQin0vrQEY0o44,38017
|
|
205
208
|
mcli/self/store_cmd.py,sha256=O6arjRr4qWQKh1QyVWtzyXq5R7yZEBL87FSI59Db7IY,13320
|
|
@@ -209,7 +212,7 @@ mcli/self/zsh_cmd.py,sha256=63jKmfjhJp2zxJL2c37OdtdzDrnOreXXfyARN7TyfzU,8294
|
|
|
209
212
|
mcli/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
210
213
|
mcli/workflow/doc_convert.py,sha256=X7ZCYbGCBZLv6WkxAgHfVOkD-6k-euo6qIaPgaKiwhA,25972
|
|
211
214
|
mcli/workflow/lsh_integration.py,sha256=jop80DUjdOSxmqPb-gX_OBep5f1twViv-pXmkcFqBPY,13314
|
|
212
|
-
mcli/workflow/workflow.py,sha256=
|
|
215
|
+
mcli/workflow/workflow.py,sha256=vqT7B1N7wEAbCLQB9xOYppJbdaDabH2mlKA6dSDL2Y0,5507
|
|
213
216
|
mcli/workflow/daemon/__init__.py,sha256=iDounKzoQhHcgHwK1kFJHnPPhn09JFzvSEpXRZDSnxU,565
|
|
214
217
|
mcli/workflow/daemon/async_command_database.py,sha256=pvfKYjt0Jg1EPwJ1p2C0M3bsBWvjEs4Ok-Y6-jY0qVI,24873
|
|
215
218
|
mcli/workflow/daemon/async_process_manager.py,sha256=hDehiYuWnBOv8LbMLTDEi4DVyrwm8YrrYF8Ca5P66A4,21374
|
|
@@ -261,16 +264,16 @@ mcli/workflow/scheduler/monitor.py,sha256=63TClZQyxbJyx5Sf3q1v8BgO6YPvWQNZYfCixM
|
|
|
261
264
|
mcli/workflow/scheduler/persistence.py,sha256=SU8-F5wTpTercZvTeAXKlGI7gwHyfmYDhX2_KnEYnjs,11592
|
|
262
265
|
mcli/workflow/scheduler/scheduler.py,sha256=1Ujq9VgL1rSTCAtshuLA2_sodW6HOj0MEZem7Ga-kic,23351
|
|
263
266
|
mcli/workflow/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
264
|
-
mcli/workflow/secrets/__init__.py,sha256=
|
|
265
|
-
mcli/workflow/secrets/secrets_cmd.py,sha256=
|
|
267
|
+
mcli/workflow/secrets/__init__.py,sha256=RxHMwOzN5vCDSA8KN-glhdX-71kuxd09acs6WxRlqvs,108
|
|
268
|
+
mcli/workflow/secrets/secrets_cmd.py,sha256=bTZDsjeOZb6pg11C1fmbDszkoAc07espJGRlkKpfHEI,6760
|
|
266
269
|
mcli/workflow/sync/__init__.py,sha256=vYjpzxC1OTFvhp2sgBqzrujUhghb4MtFNxrsPNDkUcc,104
|
|
267
270
|
mcli/workflow/sync/test_cmd.py,sha256=neVgs9zEnKSxlvzDpFkuCGucqnzjrShm2OvJtHibslg,10009
|
|
268
271
|
mcli/workflow/videos/__init__.py,sha256=aV3DEoO7qdKJY4odWKoQbOKDQq4ludTeCLnZcupOFIM,25
|
|
269
272
|
mcli/workflow/wakatime/__init__.py,sha256=wKG8cVIHVtMPhNRFGFtX43bRnocHqOMMkFMkmW-M6pU,2626
|
|
270
273
|
mcli/workflow/wakatime/wakatime.py,sha256=sEjsUKa3-XyE8Ni6sAb_D3GAY5jDcA30KknW9YTbLTA,142
|
|
271
|
-
mcli_framework-7.
|
|
272
|
-
mcli_framework-7.
|
|
273
|
-
mcli_framework-7.
|
|
274
|
-
mcli_framework-7.
|
|
275
|
-
mcli_framework-7.
|
|
276
|
-
mcli_framework-7.
|
|
274
|
+
mcli_framework-7.12.0.dist-info/licenses/LICENSE,sha256=sahwAMfrJv2-V66HNPTp7A9UmMjxtyejwTZZoWQvEcI,1075
|
|
275
|
+
mcli_framework-7.12.0.dist-info/METADATA,sha256=OohrTi2RR9BjdVJQlLsX7skMsOYiC5Gfd5EGQVCmmcY,18635
|
|
276
|
+
mcli_framework-7.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
277
|
+
mcli_framework-7.12.0.dist-info/entry_points.txt,sha256=dYrZbDIm-KUPsl1wfv600Kx_8sMy89phMkCihbDRgP8,261
|
|
278
|
+
mcli_framework-7.12.0.dist-info/top_level.txt,sha256=_bnO8J2EUkliWivey_1le0UrnocFKmyVMQjbQ8iVXjc,5
|
|
279
|
+
mcli_framework-7.12.0.dist-info/RECORD,,
|