mcli-framework 7.10.1__py3-none-any.whl → 7.11.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 +150 -58
- mcli/app/main.py +21 -27
- mcli/lib/custom_commands.py +62 -12
- mcli/lib/optional_deps.py +240 -0
- mcli/lib/paths.py +129 -5
- mcli/self/migrate_cmd.py +261 -0
- mcli/self/self_cmd.py +8 -0
- mcli/workflow/git_commit/ai_service.py +13 -2
- mcli/workflow/notebook/__init__.py +16 -0
- mcli/workflow/notebook/converter.py +375 -0
- mcli/workflow/notebook/notebook_cmd.py +441 -0
- mcli/workflow/notebook/schema.py +402 -0
- mcli/workflow/notebook/validator.py +313 -0
- mcli/workflow/secrets/__init__.py +4 -0
- mcli/workflow/secrets/secrets_cmd.py +192 -0
- mcli/workflow/workflow.py +35 -5
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/METADATA +86 -55
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/RECORD +22 -34
- mcli/ml/features/political_features.py +0 -677
- mcli/ml/preprocessing/politician_trading_preprocessor.py +0 -570
- mcli/workflow/politician_trading/__init__.py +0 -4
- mcli/workflow/politician_trading/config.py +0 -134
- mcli/workflow/politician_trading/connectivity.py +0 -492
- mcli/workflow/politician_trading/data_sources.py +0 -654
- mcli/workflow/politician_trading/database.py +0 -412
- mcli/workflow/politician_trading/demo.py +0 -249
- mcli/workflow/politician_trading/models.py +0 -327
- mcli/workflow/politician_trading/monitoring.py +0 -413
- mcli/workflow/politician_trading/scrapers.py +0 -1074
- mcli/workflow/politician_trading/scrapers_california.py +0 -434
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +0 -797
- mcli/workflow/politician_trading/scrapers_eu.py +0 -376
- mcli/workflow/politician_trading/scrapers_free_sources.py +0 -509
- mcli/workflow/politician_trading/scrapers_third_party.py +0 -373
- mcli/workflow/politician_trading/scrapers_uk.py +0 -378
- mcli/workflow/politician_trading/scrapers_us_states.py +0 -471
- mcli/workflow/politician_trading/seed_database.py +0 -520
- mcli/workflow/politician_trading/supabase_functions.py +0 -354
- mcli/workflow/politician_trading/workflow.py +0 -879
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/WHEEL +0 -0
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/top_level.txt +0 -0
mcli/app/commands_cmd.py
CHANGED
|
@@ -172,38 +172,48 @@ 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("list")
|
|
182
186
|
@click.option("--include-groups", is_flag=True, help="Include command groups in listing")
|
|
183
187
|
@click.option("--daemon-only", is_flag=True, help="Show only daemon database commands")
|
|
184
188
|
@click.option(
|
|
185
|
-
"--custom-only", is_flag=True, help="Show only custom commands from
|
|
189
|
+
"--custom-only", is_flag=True, help="Show only custom commands from command directory"
|
|
186
190
|
)
|
|
187
191
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
188
|
-
|
|
192
|
+
@click.option(
|
|
193
|
+
"--global", "-g", "is_global", is_flag=True, help="Use global commands (~/.mcli/commands/) instead of local (.mcli/commands/)"
|
|
194
|
+
)
|
|
195
|
+
def list_commands(include_groups: bool, daemon_only: bool, custom_only: bool, as_json: bool, is_global: bool):
|
|
189
196
|
"""
|
|
190
197
|
List all available commands.
|
|
191
198
|
|
|
192
199
|
By default, shows all discovered Click commands. Use flags to filter:
|
|
193
|
-
- --custom-only: Show only custom commands
|
|
200
|
+
- --custom-only: Show only custom commands
|
|
194
201
|
- --daemon-only: Show only daemon database commands
|
|
202
|
+
- --global/-g: Use global commands directory instead of local
|
|
195
203
|
|
|
196
204
|
Examples:
|
|
197
|
-
mcli commands list # Show all commands
|
|
198
|
-
mcli commands list --custom-only # Show only custom commands
|
|
205
|
+
mcli commands list # Show all commands (local if in git repo)
|
|
206
|
+
mcli commands list --custom-only # Show only custom commands (local if in git repo)
|
|
207
|
+
mcli commands list --global # Show all global commands
|
|
208
|
+
mcli commands list --custom-only -g # Show only global custom commands
|
|
199
209
|
mcli commands list --json # Output as JSON
|
|
200
210
|
"""
|
|
201
211
|
from rich.table import Table
|
|
202
212
|
|
|
203
213
|
try:
|
|
204
214
|
if custom_only:
|
|
205
|
-
# Show only custom commands
|
|
206
|
-
manager = get_command_manager()
|
|
215
|
+
# Show only custom commands
|
|
216
|
+
manager = get_command_manager(global_mode=is_global)
|
|
207
217
|
cmds = manager.load_all_commands()
|
|
208
218
|
|
|
209
219
|
if not cmds:
|
|
@@ -232,7 +242,14 @@ def list_commands(include_groups: bool, daemon_only: bool, custom_only: bool, as
|
|
|
232
242
|
)
|
|
233
243
|
|
|
234
244
|
console.print(table)
|
|
235
|
-
|
|
245
|
+
|
|
246
|
+
# Show context information
|
|
247
|
+
scope = "global" if is_global or not manager.is_local else "local"
|
|
248
|
+
scope_color = "yellow" if scope == "local" else "cyan"
|
|
249
|
+
console.print(f"\n[dim]Scope: [{scope_color}]{scope}[/{scope_color}][/dim]")
|
|
250
|
+
if manager.is_local and manager.git_root:
|
|
251
|
+
console.print(f"[dim]Git repository: {manager.git_root}[/dim]")
|
|
252
|
+
console.print(f"[dim]Commands directory: {manager.commands_dir}[/dim]")
|
|
236
253
|
console.print(f"[dim]Lockfile: {manager.lockfile_path}[/dim]")
|
|
237
254
|
|
|
238
255
|
return 0
|
|
@@ -287,12 +304,19 @@ def list_commands(include_groups: bool, daemon_only: bool, custom_only: bool, as
|
|
|
287
304
|
console.print(f"[red]Error: {e}[/red]")
|
|
288
305
|
|
|
289
306
|
|
|
290
|
-
@
|
|
307
|
+
@workflow.command("search")
|
|
291
308
|
@click.argument("query")
|
|
292
309
|
@click.option("--daemon-only", is_flag=True, help="Search only daemon database commands")
|
|
293
310
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
294
|
-
|
|
295
|
-
"""Search commands
|
|
311
|
+
@click.option(
|
|
312
|
+
"--global", "-g", "is_global", is_flag=True, help="Search global commands instead of local"
|
|
313
|
+
)
|
|
314
|
+
def search_commands(query: str, daemon_only: bool, as_json: bool, is_global: bool):
|
|
315
|
+
"""
|
|
316
|
+
Search commands by name, description, or tags.
|
|
317
|
+
|
|
318
|
+
By default searches local commands (if in git repo), use --global/-g for global commands.
|
|
319
|
+
"""
|
|
296
320
|
try:
|
|
297
321
|
if daemon_only:
|
|
298
322
|
# Search only daemon database commands
|
|
@@ -358,7 +382,7 @@ def search_commands(query: str, daemon_only: bool, as_json: bool):
|
|
|
358
382
|
console.print(f"[red]Error: {e}[/red]")
|
|
359
383
|
|
|
360
384
|
|
|
361
|
-
@
|
|
385
|
+
@workflow.command("execute")
|
|
362
386
|
@click.argument("command_name")
|
|
363
387
|
@click.argument("args", nargs=-1)
|
|
364
388
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
@@ -388,7 +412,7 @@ def execute_command(command_name: str, args: tuple, as_json: bool, timeout: Opti
|
|
|
388
412
|
console.print(f"[red]Error: {e}[/red]")
|
|
389
413
|
|
|
390
414
|
|
|
391
|
-
@
|
|
415
|
+
@workflow.command("info")
|
|
392
416
|
@click.argument("command_name")
|
|
393
417
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
394
418
|
def command_info(command_name: str, as_json: bool):
|
|
@@ -676,9 +700,9 @@ logger = get_logger()
|
|
|
676
700
|
pass
|
|
677
701
|
|
|
678
702
|
|
|
679
|
-
@
|
|
703
|
+
@workflow.command("add")
|
|
680
704
|
@click.argument("command_name", required=True)
|
|
681
|
-
@click.option("--group",
|
|
705
|
+
@click.option("--group", help="Command group (defaults to 'workflow')", default="workflow")
|
|
682
706
|
@click.option("--description", "-d", help="Description for the command", default="Custom command")
|
|
683
707
|
@click.option(
|
|
684
708
|
"--template",
|
|
@@ -699,7 +723,10 @@ logger = get_logger()
|
|
|
699
723
|
type=click.Choice(["bash", "zsh", "fish", "sh"], case_sensitive=False),
|
|
700
724
|
help="Shell type for shell commands (defaults to $SHELL)",
|
|
701
725
|
)
|
|
702
|
-
|
|
726
|
+
@click.option(
|
|
727
|
+
"--global", "-g", "is_global", is_flag=True, help="Add to global commands (~/.mcli/commands/) instead of local (.mcli/commands/)"
|
|
728
|
+
)
|
|
729
|
+
def add_command(command_name, group, description, template, language, shell, is_global):
|
|
703
730
|
"""
|
|
704
731
|
Generate a new portable custom command saved to ~/.mcli/commands/.
|
|
705
732
|
|
|
@@ -749,7 +776,7 @@ def add_command(command_name, group, description, template, language, shell):
|
|
|
749
776
|
command_group = "workflow" # Default to workflow group
|
|
750
777
|
|
|
751
778
|
# Get the command manager
|
|
752
|
-
manager = get_command_manager()
|
|
779
|
+
manager = get_command_manager(global_mode=is_global)
|
|
753
780
|
|
|
754
781
|
# Check if command already exists
|
|
755
782
|
command_file = manager.commands_dir / f"{command_name}.json"
|
|
@@ -830,29 +857,45 @@ def add_command(command_name, group, description, template, language, shell):
|
|
|
830
857
|
)
|
|
831
858
|
|
|
832
859
|
lang_display = f"{language}" if language == "python" else f"{language} ({shell})"
|
|
833
|
-
|
|
860
|
+
scope = "global" if is_global or not manager.is_local else "local"
|
|
861
|
+
scope_display = f"[cyan]{scope}[/cyan]" if scope == "global" else f"[yellow]{scope}[/yellow]"
|
|
862
|
+
|
|
863
|
+
logger.info(f"Created portable custom command: {command_name} ({lang_display}) [{scope}]")
|
|
834
864
|
console.print(
|
|
835
|
-
f"[green]Created portable custom command: {command_name}[/green] [dim]({lang_display})[/dim]"
|
|
865
|
+
f"[green]Created portable custom command: {command_name}[/green] [dim]({lang_display}) [Scope: {scope_display}][/dim]"
|
|
836
866
|
)
|
|
837
867
|
console.print(f"[dim]Saved to: {saved_path}[/dim]")
|
|
868
|
+
if manager.is_local and manager.git_root:
|
|
869
|
+
console.print(f"[dim]Git repository: {manager.git_root}[/dim]")
|
|
838
870
|
console.print(f"[dim]Group: {command_group}[/dim]")
|
|
839
871
|
console.print(f"[dim]Execute with: mcli {command_group} {command_name}[/dim]")
|
|
840
872
|
console.print("[dim]Command will be automatically loaded on next mcli startup[/dim]")
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
873
|
+
|
|
874
|
+
if scope == "global":
|
|
875
|
+
console.print(
|
|
876
|
+
f"[dim]You can share this command by copying {saved_path} to another machine's ~/.mcli/commands/ directory[/dim]"
|
|
877
|
+
)
|
|
878
|
+
else:
|
|
879
|
+
console.print(
|
|
880
|
+
f"[dim]This command is local to this git repository. Use --global/-g to create global commands.[/dim]"
|
|
881
|
+
)
|
|
844
882
|
|
|
845
883
|
return 0
|
|
846
884
|
|
|
847
885
|
|
|
848
|
-
@
|
|
886
|
+
@workflow.command("remove")
|
|
849
887
|
@click.argument("command_name", required=True)
|
|
850
888
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
851
|
-
|
|
889
|
+
@click.option(
|
|
890
|
+
"--global", "-g", "is_global", is_flag=True, help="Remove from global commands instead of local"
|
|
891
|
+
)
|
|
892
|
+
def remove_command(command_name, yes, is_global):
|
|
852
893
|
"""
|
|
853
|
-
Remove a custom command
|
|
894
|
+
Remove a custom command.
|
|
895
|
+
|
|
896
|
+
By default removes from local commands (if in git repo), use --global/-g for global commands.
|
|
854
897
|
"""
|
|
855
|
-
manager = get_command_manager()
|
|
898
|
+
manager = get_command_manager(global_mode=is_global)
|
|
856
899
|
command_file = manager.commands_dir / f"{command_name}.json"
|
|
857
900
|
|
|
858
901
|
if not command_file.exists():
|
|
@@ -875,7 +918,7 @@ def remove_command(command_name, yes):
|
|
|
875
918
|
return 1
|
|
876
919
|
|
|
877
920
|
|
|
878
|
-
@
|
|
921
|
+
@workflow.command("export")
|
|
879
922
|
@click.argument("target", type=click.Path(), required=False)
|
|
880
923
|
@click.option(
|
|
881
924
|
"--script", "-s", is_flag=True, help="Export as Python script (requires command name)"
|
|
@@ -884,7 +927,10 @@ def remove_command(command_name, yes):
|
|
|
884
927
|
"--standalone", is_flag=True, help="Make script standalone (add if __name__ == '__main__')"
|
|
885
928
|
)
|
|
886
929
|
@click.option("--output", "-o", type=click.Path(), help="Output file path")
|
|
887
|
-
|
|
930
|
+
@click.option(
|
|
931
|
+
"--global", "-g", "is_global", is_flag=True, help="Export from global commands instead of local"
|
|
932
|
+
)
|
|
933
|
+
def export_commands(target, script, standalone, output, is_global):
|
|
888
934
|
"""
|
|
889
935
|
Export custom commands to JSON file or export a single command to Python script.
|
|
890
936
|
|
|
@@ -892,12 +938,13 @@ def export_commands(target, script, standalone, output):
|
|
|
892
938
|
With --script/-s: Export a single command to Python script
|
|
893
939
|
|
|
894
940
|
Examples:
|
|
895
|
-
mcli commands export # Export all
|
|
941
|
+
mcli commands export # Export all local commands
|
|
942
|
+
mcli commands export --global # Export all global commands
|
|
896
943
|
mcli commands export my-export.json # Export all to specified file
|
|
897
944
|
mcli commands export my-cmd -s # Export command to my-cmd.py
|
|
898
945
|
mcli commands export my-cmd -s -o out.py --standalone
|
|
899
946
|
"""
|
|
900
|
-
manager = get_command_manager()
|
|
947
|
+
manager = get_command_manager(global_mode=is_global)
|
|
901
948
|
|
|
902
949
|
if script:
|
|
903
950
|
# Export single command to Python script
|
|
@@ -972,12 +1019,12 @@ def export_commands(target, script, standalone, output):
|
|
|
972
1019
|
return 1
|
|
973
1020
|
|
|
974
1021
|
|
|
975
|
-
@
|
|
1022
|
+
@workflow.command("import")
|
|
976
1023
|
@click.argument("source", type=click.Path(exists=True), required=True)
|
|
977
1024
|
@click.option("--script", "-s", is_flag=True, help="Import from Python script")
|
|
978
1025
|
@click.option("--overwrite", is_flag=True, help="Overwrite existing commands")
|
|
979
1026
|
@click.option("--name", "-n", help="Command name (for script import, defaults to script filename)")
|
|
980
|
-
@click.option("--group",
|
|
1027
|
+
@click.option("--group", default="workflow", help="Command group (for script import)")
|
|
981
1028
|
@click.option("--description", "-d", help="Command description (for script import)")
|
|
982
1029
|
@click.option(
|
|
983
1030
|
"--interactive",
|
|
@@ -985,7 +1032,10 @@ def export_commands(target, script, standalone, output):
|
|
|
985
1032
|
is_flag=True,
|
|
986
1033
|
help="Open in $EDITOR for review/editing (for script import)",
|
|
987
1034
|
)
|
|
988
|
-
|
|
1035
|
+
@click.option(
|
|
1036
|
+
"--global", "-g", "is_global", is_flag=True, help="Import to global commands instead of local"
|
|
1037
|
+
)
|
|
1038
|
+
def import_commands(source, script, overwrite, name, group, description, interactive, is_global):
|
|
989
1039
|
"""
|
|
990
1040
|
Import custom commands from JSON file or import a Python script as a command.
|
|
991
1041
|
|
|
@@ -993,13 +1043,14 @@ def import_commands(source, script, overwrite, name, group, description, interac
|
|
|
993
1043
|
With --script/-s: Import a Python script as a command
|
|
994
1044
|
|
|
995
1045
|
Examples:
|
|
996
|
-
mcli commands import commands-export.json
|
|
1046
|
+
mcli commands import commands-export.json # Import to local (if in git repo)
|
|
1047
|
+
mcli commands import commands-export.json --global # Import to global
|
|
997
1048
|
mcli commands import my_script.py -s
|
|
998
1049
|
mcli commands import my_script.py -s --name custom-cmd --interactive
|
|
999
1050
|
"""
|
|
1000
1051
|
import subprocess
|
|
1001
1052
|
|
|
1002
|
-
manager = get_command_manager()
|
|
1053
|
+
manager = get_command_manager(global_mode=is_global)
|
|
1003
1054
|
source_path = Path(source)
|
|
1004
1055
|
|
|
1005
1056
|
if script:
|
|
@@ -1142,12 +1193,17 @@ def import_commands(source, script, overwrite, name, group, description, interac
|
|
|
1142
1193
|
return 0
|
|
1143
1194
|
|
|
1144
1195
|
|
|
1145
|
-
@
|
|
1146
|
-
|
|
1196
|
+
@workflow.command("verify")
|
|
1197
|
+
@click.option(
|
|
1198
|
+
"--global", "-g", "is_global", is_flag=True, help="Verify global commands instead of local"
|
|
1199
|
+
)
|
|
1200
|
+
def verify_commands(is_global):
|
|
1147
1201
|
"""
|
|
1148
1202
|
Verify that custom commands match the lockfile.
|
|
1203
|
+
|
|
1204
|
+
By default verifies local commands (if in git repo), use --global/-g for global commands.
|
|
1149
1205
|
"""
|
|
1150
|
-
manager = get_command_manager()
|
|
1206
|
+
manager = get_command_manager(global_mode=is_global)
|
|
1151
1207
|
|
|
1152
1208
|
# First, ensure lockfile is up to date
|
|
1153
1209
|
manager.update_lockfile()
|
|
@@ -1180,12 +1236,17 @@ def verify_commands():
|
|
|
1180
1236
|
return 1
|
|
1181
1237
|
|
|
1182
1238
|
|
|
1183
|
-
@
|
|
1184
|
-
|
|
1239
|
+
@workflow.command("update-lockfile")
|
|
1240
|
+
@click.option(
|
|
1241
|
+
"--global", "-g", "is_global", is_flag=True, help="Update global lockfile instead of local"
|
|
1242
|
+
)
|
|
1243
|
+
def update_lockfile(is_global):
|
|
1185
1244
|
"""
|
|
1186
1245
|
Update the commands lockfile with current state.
|
|
1246
|
+
|
|
1247
|
+
By default updates local lockfile (if in git repo), use --global/-g for global lockfile.
|
|
1187
1248
|
"""
|
|
1188
|
-
manager = get_command_manager()
|
|
1249
|
+
manager = get_command_manager(global_mode=is_global)
|
|
1189
1250
|
|
|
1190
1251
|
if manager.update_lockfile():
|
|
1191
1252
|
console.print(f"[green]Updated lockfile: {manager.lockfile_path}[/green]")
|
|
@@ -1195,10 +1256,13 @@ def update_lockfile():
|
|
|
1195
1256
|
return 1
|
|
1196
1257
|
|
|
1197
1258
|
|
|
1198
|
-
@
|
|
1259
|
+
@workflow.command("edit")
|
|
1199
1260
|
@click.argument("command_name")
|
|
1200
1261
|
@click.option("--editor", "-e", help="Editor to use (defaults to $EDITOR)")
|
|
1201
|
-
|
|
1262
|
+
@click.option(
|
|
1263
|
+
"--global", "-g", "is_global", is_flag=True, help="Edit global command instead of local"
|
|
1264
|
+
)
|
|
1265
|
+
def edit_command(command_name, editor, is_global):
|
|
1202
1266
|
"""
|
|
1203
1267
|
Edit a command interactively using $EDITOR.
|
|
1204
1268
|
|
|
@@ -1206,12 +1270,13 @@ def edit_command(command_name, editor):
|
|
|
1206
1270
|
allows you to make changes, and saves the updated version.
|
|
1207
1271
|
|
|
1208
1272
|
Examples:
|
|
1209
|
-
mcli commands edit my-command
|
|
1273
|
+
mcli commands edit my-command # Edit local command (if in git repo)
|
|
1274
|
+
mcli commands edit my-command --global # Edit global command
|
|
1210
1275
|
mcli commands edit my-command --editor code
|
|
1211
1276
|
"""
|
|
1212
1277
|
import subprocess
|
|
1213
1278
|
|
|
1214
|
-
manager = get_command_manager()
|
|
1279
|
+
manager = get_command_manager(global_mode=is_global)
|
|
1215
1280
|
|
|
1216
1281
|
# Load the command
|
|
1217
1282
|
command_file = manager.commands_dir / f"{command_name}.json"
|
|
@@ -1294,7 +1359,7 @@ def edit_command(command_name, editor):
|
|
|
1294
1359
|
# Moved from mcli.self for better organization
|
|
1295
1360
|
|
|
1296
1361
|
|
|
1297
|
-
@
|
|
1362
|
+
@workflow.group("state")
|
|
1298
1363
|
def command_state():
|
|
1299
1364
|
"""Manage command state lockfile and history."""
|
|
1300
1365
|
pass
|
|
@@ -1379,7 +1444,7 @@ def _get_store_path() -> Path:
|
|
|
1379
1444
|
return DEFAULT_STORE_PATH
|
|
1380
1445
|
|
|
1381
1446
|
|
|
1382
|
-
@
|
|
1447
|
+
@workflow.group("store")
|
|
1383
1448
|
def store():
|
|
1384
1449
|
"""Manage command store - sync ~/.mcli/commands/ to git"""
|
|
1385
1450
|
pass
|
|
@@ -1466,10 +1531,19 @@ Last updated: {datetime.now().isoformat()}
|
|
|
1466
1531
|
@store.command(name="push")
|
|
1467
1532
|
@click.option("--message", "-m", help="Commit message")
|
|
1468
1533
|
@click.option("--all", "-a", is_flag=True, help="Push all files (including backups)")
|
|
1469
|
-
|
|
1470
|
-
"""Push
|
|
1534
|
+
@click.option(
|
|
1535
|
+
"--global", "-g", "is_global", is_flag=True, help="Push global commands instead of local"
|
|
1536
|
+
)
|
|
1537
|
+
def push_commands(message, all, is_global):
|
|
1538
|
+
"""
|
|
1539
|
+
Push commands to git store.
|
|
1540
|
+
|
|
1541
|
+
By default pushes local commands (if in git repo), use --global/-g for global commands.
|
|
1542
|
+
"""
|
|
1471
1543
|
try:
|
|
1472
1544
|
store_path = _get_store_path()
|
|
1545
|
+
from mcli.lib.paths import get_custom_commands_dir
|
|
1546
|
+
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
1473
1547
|
|
|
1474
1548
|
# Copy commands to store
|
|
1475
1549
|
info(f"Copying commands from {COMMANDS_PATH} to {store_path}...")
|
|
@@ -1521,10 +1595,19 @@ def push_commands(message, all):
|
|
|
1521
1595
|
|
|
1522
1596
|
@store.command(name="pull")
|
|
1523
1597
|
@click.option("--force", "-f", is_flag=True, help="Overwrite local commands without backup")
|
|
1524
|
-
|
|
1525
|
-
"""Pull
|
|
1598
|
+
@click.option(
|
|
1599
|
+
"--global", "-g", "is_global", is_flag=True, help="Pull to global commands instead of local"
|
|
1600
|
+
)
|
|
1601
|
+
def pull_commands(force, is_global):
|
|
1602
|
+
"""
|
|
1603
|
+
Pull commands from git store.
|
|
1604
|
+
|
|
1605
|
+
By default pulls to local commands (if in git repo), use --global/-g for global commands.
|
|
1606
|
+
"""
|
|
1526
1607
|
try:
|
|
1527
1608
|
store_path = _get_store_path()
|
|
1609
|
+
from mcli.lib.paths import get_custom_commands_dir
|
|
1610
|
+
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
1528
1611
|
|
|
1529
1612
|
# Pull from remote
|
|
1530
1613
|
try:
|
|
@@ -1569,10 +1652,19 @@ def pull_commands(force):
|
|
|
1569
1652
|
|
|
1570
1653
|
@store.command(name="sync")
|
|
1571
1654
|
@click.option("--message", "-m", help="Commit message if pushing")
|
|
1572
|
-
|
|
1573
|
-
"""
|
|
1655
|
+
@click.option(
|
|
1656
|
+
"--global", "-g", "is_global", is_flag=True, help="Sync global commands instead of local"
|
|
1657
|
+
)
|
|
1658
|
+
def sync_commands(message, is_global):
|
|
1659
|
+
"""
|
|
1660
|
+
Sync commands bidirectionally (pull then push if changes).
|
|
1661
|
+
|
|
1662
|
+
By default syncs local commands (if in git repo), use --global/-g for global commands.
|
|
1663
|
+
"""
|
|
1574
1664
|
try:
|
|
1575
1665
|
store_path = _get_store_path()
|
|
1666
|
+
from mcli.lib.paths import get_custom_commands_dir
|
|
1667
|
+
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
1576
1668
|
|
|
1577
1669
|
# First pull
|
|
1578
1670
|
info("Pulling latest changes...")
|
|
@@ -1769,7 +1861,7 @@ def show_command(command_name, store):
|
|
|
1769
1861
|
# Moved from mcli.self for better organization
|
|
1770
1862
|
|
|
1771
1863
|
|
|
1772
|
-
@
|
|
1864
|
+
@workflow.command("extract-workflow-commands")
|
|
1773
1865
|
@click.option(
|
|
1774
1866
|
"--output", "-o", type=click.Path(), help="Output file (default: workflow-commands.json)"
|
|
1775
1867
|
)
|
mcli/app/main.py
CHANGED
|
@@ -326,14 +326,14 @@ class LazyGroup(click.Group):
|
|
|
326
326
|
|
|
327
327
|
def _add_lazy_commands(app: click.Group):
|
|
328
328
|
"""Add command groups with lazy loading."""
|
|
329
|
-
#
|
|
329
|
+
# Workflow management - load immediately for fast access (renamed from 'commands')
|
|
330
330
|
try:
|
|
331
|
-
from mcli.app.commands_cmd import
|
|
331
|
+
from mcli.app.commands_cmd import workflow
|
|
332
332
|
|
|
333
|
-
app.add_command(
|
|
334
|
-
logger.debug("Added
|
|
333
|
+
app.add_command(workflow, name="workflow")
|
|
334
|
+
logger.debug("Added workflow management group")
|
|
335
335
|
except ImportError as e:
|
|
336
|
-
logger.debug(f"Could not load
|
|
336
|
+
logger.debug(f"Could not load workflow management group: {e}")
|
|
337
337
|
|
|
338
338
|
# Self management - load immediately as it's commonly used
|
|
339
339
|
try:
|
|
@@ -344,35 +344,29 @@ def _add_lazy_commands(app: click.Group):
|
|
|
344
344
|
except Exception as e:
|
|
345
345
|
logger.debug(f"Could not load self commands: {e}")
|
|
346
346
|
|
|
347
|
-
#
|
|
348
|
-
|
|
349
|
-
from mcli.lib.lib import lib
|
|
350
|
-
|
|
351
|
-
app.add_command(lib, name="lib")
|
|
352
|
-
logger.debug("Added lib commands")
|
|
353
|
-
except Exception as e:
|
|
354
|
-
logger.debug(f"Could not load lib commands: {e}")
|
|
347
|
+
# Note: lib group removed - secrets moved to workflows
|
|
348
|
+
# Previous: mcli lib secrets -> Now: mcli workflows secrets
|
|
355
349
|
|
|
356
|
-
# Add workflow with completion-aware lazy loading
|
|
350
|
+
# Add workflows group (renamed from 'workflow') with completion-aware lazy loading
|
|
357
351
|
try:
|
|
358
352
|
from mcli.app.completion_helpers import create_completion_aware_lazy_group
|
|
359
353
|
|
|
360
|
-
|
|
361
|
-
"
|
|
362
|
-
"mcli.workflow.workflow.
|
|
363
|
-
"
|
|
354
|
+
workflows_group = create_completion_aware_lazy_group(
|
|
355
|
+
"workflows",
|
|
356
|
+
"mcli.workflow.workflow.workflows",
|
|
357
|
+
"Runnable workflows for automation, video processing, and daemon management",
|
|
364
358
|
)
|
|
365
|
-
app.add_command(
|
|
366
|
-
logger.debug("Added completion-aware
|
|
359
|
+
app.add_command(workflows_group, name="workflows")
|
|
360
|
+
logger.debug("Added completion-aware workflows group")
|
|
367
361
|
except ImportError as e:
|
|
368
362
|
logger.debug(f"Could not load completion helpers, using standard lazy group: {e}")
|
|
369
363
|
# Fallback to standard lazy group
|
|
370
|
-
|
|
371
|
-
"
|
|
372
|
-
"mcli.workflow.workflow.
|
|
373
|
-
help="
|
|
364
|
+
workflows_group = LazyGroup(
|
|
365
|
+
"workflows",
|
|
366
|
+
"mcli.workflow.workflow.workflows",
|
|
367
|
+
help="Runnable workflows for automation, video processing, and daemon management",
|
|
374
368
|
)
|
|
375
|
-
app.add_command(
|
|
369
|
+
app.add_command(workflows_group, name="workflows")
|
|
376
370
|
|
|
377
371
|
# Lazy load other heavy commands that are used less frequently
|
|
378
372
|
# NOTE: chat and model commands have been removed
|
|
@@ -382,8 +376,8 @@ def _add_lazy_commands(app: click.Group):
|
|
|
382
376
|
lazy_commands = {}
|
|
383
377
|
|
|
384
378
|
for cmd_name, cmd_info in lazy_commands.items():
|
|
385
|
-
# Skip
|
|
386
|
-
if cmd_name == "
|
|
379
|
+
# Skip workflows since we already added it with completion support
|
|
380
|
+
if cmd_name == "workflows":
|
|
387
381
|
continue
|
|
388
382
|
|
|
389
383
|
if cmd_name in ["model"]:
|
mcli/lib/custom_commands.py
CHANGED
|
@@ -19,7 +19,12 @@ from typing import Any, Dict, List, Optional
|
|
|
19
19
|
import click
|
|
20
20
|
|
|
21
21
|
from mcli.lib.logger.logger import get_logger, register_subprocess
|
|
22
|
-
from mcli.lib.paths import
|
|
22
|
+
from mcli.lib.paths import (
|
|
23
|
+
get_custom_commands_dir,
|
|
24
|
+
get_lockfile_path,
|
|
25
|
+
is_git_repository,
|
|
26
|
+
get_git_root,
|
|
27
|
+
)
|
|
23
28
|
|
|
24
29
|
logger = get_logger()
|
|
25
30
|
|
|
@@ -27,10 +32,22 @@ logger = get_logger()
|
|
|
27
32
|
class CustomCommandManager:
|
|
28
33
|
"""Manages custom user commands stored in JSON format."""
|
|
29
34
|
|
|
30
|
-
def __init__(self):
|
|
31
|
-
|
|
35
|
+
def __init__(self, global_mode: bool = False):
|
|
36
|
+
"""
|
|
37
|
+
Initialize the custom command manager.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
global_mode: If True, use global commands directory (~/.mcli/commands/).
|
|
41
|
+
If False, use local directory (.mcli/commands/) when in a git repository.
|
|
42
|
+
"""
|
|
43
|
+
self.global_mode = global_mode
|
|
44
|
+
self.commands_dir = get_custom_commands_dir(global_mode=global_mode)
|
|
32
45
|
self.loaded_commands: Dict[str, Any] = {}
|
|
33
|
-
self.lockfile_path =
|
|
46
|
+
self.lockfile_path = get_lockfile_path(global_mode=global_mode)
|
|
47
|
+
|
|
48
|
+
# Store context information for display
|
|
49
|
+
self.is_local = not global_mode and is_git_repository()
|
|
50
|
+
self.git_root = get_git_root() if self.is_local else None
|
|
34
51
|
|
|
35
52
|
def save_command(
|
|
36
53
|
self,
|
|
@@ -107,15 +124,25 @@ class CustomCommandManager:
|
|
|
107
124
|
"""
|
|
108
125
|
Load all custom commands from the commands directory.
|
|
109
126
|
|
|
127
|
+
Automatically filters out test commands (starting with 'test_' or 'test-')
|
|
128
|
+
unless MCLI_INCLUDE_TEST_COMMANDS=true is set.
|
|
129
|
+
|
|
110
130
|
Returns:
|
|
111
131
|
List of command data dictionaries
|
|
112
132
|
"""
|
|
113
133
|
commands = []
|
|
134
|
+
include_test = os.environ.get('MCLI_INCLUDE_TEST_COMMANDS', 'false').lower() == 'true'
|
|
135
|
+
|
|
114
136
|
for command_file in self.commands_dir.glob("*.json"):
|
|
115
137
|
# Skip the lockfile
|
|
116
138
|
if command_file.name == "commands.lock.json":
|
|
117
139
|
continue
|
|
118
140
|
|
|
141
|
+
# Skip test commands unless explicitly included
|
|
142
|
+
if not include_test and command_file.stem.startswith(('test_', 'test-')):
|
|
143
|
+
logger.debug(f"Skipping test command: {command_file.name}")
|
|
144
|
+
continue
|
|
145
|
+
|
|
119
146
|
command_data = self.load_command(command_file)
|
|
120
147
|
if command_data:
|
|
121
148
|
commands.append(command_data)
|
|
@@ -476,16 +503,39 @@ class CustomCommandManager:
|
|
|
476
503
|
return results
|
|
477
504
|
|
|
478
505
|
|
|
479
|
-
# Global
|
|
480
|
-
|
|
506
|
+
# Global and local instances
|
|
507
|
+
_global_command_manager: Optional[CustomCommandManager] = None
|
|
508
|
+
_local_command_manager: Optional[CustomCommandManager] = None
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def get_command_manager(global_mode: bool = False) -> CustomCommandManager:
|
|
512
|
+
"""
|
|
513
|
+
Get the custom command manager instance.
|
|
481
514
|
|
|
515
|
+
Args:
|
|
516
|
+
global_mode: If True, return global manager. If False, return local manager (if in git repo).
|
|
482
517
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
518
|
+
Returns:
|
|
519
|
+
CustomCommandManager instance for the appropriate scope
|
|
520
|
+
"""
|
|
521
|
+
global _global_command_manager, _local_command_manager
|
|
522
|
+
|
|
523
|
+
if global_mode:
|
|
524
|
+
if _global_command_manager is None:
|
|
525
|
+
_global_command_manager = CustomCommandManager(global_mode=True)
|
|
526
|
+
return _global_command_manager
|
|
527
|
+
else:
|
|
528
|
+
# Use local manager if in git repository
|
|
529
|
+
if is_git_repository():
|
|
530
|
+
# Recreate local manager if git root changed (e.g., changed directory)
|
|
531
|
+
if _local_command_manager is None or _local_command_manager.git_root != get_git_root():
|
|
532
|
+
_local_command_manager = CustomCommandManager(global_mode=False)
|
|
533
|
+
return _local_command_manager
|
|
534
|
+
else:
|
|
535
|
+
# Fallback to global manager when not in a git repository
|
|
536
|
+
if _global_command_manager is None:
|
|
537
|
+
_global_command_manager = CustomCommandManager(global_mode=True)
|
|
538
|
+
return _global_command_manager
|
|
489
539
|
|
|
490
540
|
|
|
491
541
|
def load_custom_commands(target_group: click.Group) -> int:
|