mcli-framework 7.0.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.

Files changed (186) hide show
  1. mcli/app/chat_cmd.py +42 -0
  2. mcli/app/commands_cmd.py +226 -0
  3. mcli/app/completion_cmd.py +216 -0
  4. mcli/app/completion_helpers.py +288 -0
  5. mcli/app/cron_test_cmd.py +697 -0
  6. mcli/app/logs_cmd.py +419 -0
  7. mcli/app/main.py +492 -0
  8. mcli/app/model/model.py +1060 -0
  9. mcli/app/model_cmd.py +227 -0
  10. mcli/app/redis_cmd.py +269 -0
  11. mcli/app/video/video.py +1114 -0
  12. mcli/app/visual_cmd.py +303 -0
  13. mcli/chat/chat.py +2409 -0
  14. mcli/chat/command_rag.py +514 -0
  15. mcli/chat/enhanced_chat.py +652 -0
  16. mcli/chat/system_controller.py +1010 -0
  17. mcli/chat/system_integration.py +1016 -0
  18. mcli/cli.py +25 -0
  19. mcli/config.toml +20 -0
  20. mcli/lib/api/api.py +586 -0
  21. mcli/lib/api/daemon_client.py +203 -0
  22. mcli/lib/api/daemon_client_local.py +44 -0
  23. mcli/lib/api/daemon_decorator.py +217 -0
  24. mcli/lib/api/mcli_decorators.py +1032 -0
  25. mcli/lib/auth/auth.py +85 -0
  26. mcli/lib/auth/aws_manager.py +85 -0
  27. mcli/lib/auth/azure_manager.py +91 -0
  28. mcli/lib/auth/credential_manager.py +192 -0
  29. mcli/lib/auth/gcp_manager.py +93 -0
  30. mcli/lib/auth/key_manager.py +117 -0
  31. mcli/lib/auth/mcli_manager.py +93 -0
  32. mcli/lib/auth/token_manager.py +75 -0
  33. mcli/lib/auth/token_util.py +1011 -0
  34. mcli/lib/config/config.py +47 -0
  35. mcli/lib/discovery/__init__.py +1 -0
  36. mcli/lib/discovery/command_discovery.py +274 -0
  37. mcli/lib/erd/erd.py +1345 -0
  38. mcli/lib/erd/generate_graph.py +453 -0
  39. mcli/lib/files/files.py +76 -0
  40. mcli/lib/fs/fs.py +109 -0
  41. mcli/lib/lib.py +29 -0
  42. mcli/lib/logger/logger.py +611 -0
  43. mcli/lib/performance/optimizer.py +409 -0
  44. mcli/lib/performance/rust_bridge.py +502 -0
  45. mcli/lib/performance/uvloop_config.py +154 -0
  46. mcli/lib/pickles/pickles.py +50 -0
  47. mcli/lib/search/cached_vectorizer.py +479 -0
  48. mcli/lib/services/data_pipeline.py +460 -0
  49. mcli/lib/services/lsh_client.py +441 -0
  50. mcli/lib/services/redis_service.py +387 -0
  51. mcli/lib/shell/shell.py +137 -0
  52. mcli/lib/toml/toml.py +33 -0
  53. mcli/lib/ui/styling.py +47 -0
  54. mcli/lib/ui/visual_effects.py +634 -0
  55. mcli/lib/watcher/watcher.py +185 -0
  56. mcli/ml/api/app.py +215 -0
  57. mcli/ml/api/middleware.py +224 -0
  58. mcli/ml/api/routers/admin_router.py +12 -0
  59. mcli/ml/api/routers/auth_router.py +244 -0
  60. mcli/ml/api/routers/backtest_router.py +12 -0
  61. mcli/ml/api/routers/data_router.py +12 -0
  62. mcli/ml/api/routers/model_router.py +302 -0
  63. mcli/ml/api/routers/monitoring_router.py +12 -0
  64. mcli/ml/api/routers/portfolio_router.py +12 -0
  65. mcli/ml/api/routers/prediction_router.py +267 -0
  66. mcli/ml/api/routers/trade_router.py +12 -0
  67. mcli/ml/api/routers/websocket_router.py +76 -0
  68. mcli/ml/api/schemas.py +64 -0
  69. mcli/ml/auth/auth_manager.py +425 -0
  70. mcli/ml/auth/models.py +154 -0
  71. mcli/ml/auth/permissions.py +302 -0
  72. mcli/ml/backtesting/backtest_engine.py +502 -0
  73. mcli/ml/backtesting/performance_metrics.py +393 -0
  74. mcli/ml/cache.py +400 -0
  75. mcli/ml/cli/main.py +398 -0
  76. mcli/ml/config/settings.py +394 -0
  77. mcli/ml/configs/dvc_config.py +230 -0
  78. mcli/ml/configs/mlflow_config.py +131 -0
  79. mcli/ml/configs/mlops_manager.py +293 -0
  80. mcli/ml/dashboard/app.py +532 -0
  81. mcli/ml/dashboard/app_integrated.py +738 -0
  82. mcli/ml/dashboard/app_supabase.py +560 -0
  83. mcli/ml/dashboard/app_training.py +615 -0
  84. mcli/ml/dashboard/cli.py +51 -0
  85. mcli/ml/data_ingestion/api_connectors.py +501 -0
  86. mcli/ml/data_ingestion/data_pipeline.py +567 -0
  87. mcli/ml/data_ingestion/stream_processor.py +512 -0
  88. mcli/ml/database/migrations/env.py +94 -0
  89. mcli/ml/database/models.py +667 -0
  90. mcli/ml/database/session.py +200 -0
  91. mcli/ml/experimentation/ab_testing.py +845 -0
  92. mcli/ml/features/ensemble_features.py +607 -0
  93. mcli/ml/features/political_features.py +676 -0
  94. mcli/ml/features/recommendation_engine.py +809 -0
  95. mcli/ml/features/stock_features.py +573 -0
  96. mcli/ml/features/test_feature_engineering.py +346 -0
  97. mcli/ml/logging.py +85 -0
  98. mcli/ml/mlops/data_versioning.py +518 -0
  99. mcli/ml/mlops/experiment_tracker.py +377 -0
  100. mcli/ml/mlops/model_serving.py +481 -0
  101. mcli/ml/mlops/pipeline_orchestrator.py +614 -0
  102. mcli/ml/models/base_models.py +324 -0
  103. mcli/ml/models/ensemble_models.py +675 -0
  104. mcli/ml/models/recommendation_models.py +474 -0
  105. mcli/ml/models/test_models.py +487 -0
  106. mcli/ml/monitoring/drift_detection.py +676 -0
  107. mcli/ml/monitoring/metrics.py +45 -0
  108. mcli/ml/optimization/portfolio_optimizer.py +834 -0
  109. mcli/ml/preprocessing/data_cleaners.py +451 -0
  110. mcli/ml/preprocessing/feature_extractors.py +491 -0
  111. mcli/ml/preprocessing/ml_pipeline.py +382 -0
  112. mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
  113. mcli/ml/preprocessing/test_preprocessing.py +294 -0
  114. mcli/ml/scripts/populate_sample_data.py +200 -0
  115. mcli/ml/tasks.py +400 -0
  116. mcli/ml/tests/test_integration.py +429 -0
  117. mcli/ml/tests/test_training_dashboard.py +387 -0
  118. mcli/public/oi/oi.py +15 -0
  119. mcli/public/public.py +4 -0
  120. mcli/self/self_cmd.py +1246 -0
  121. mcli/workflow/daemon/api_daemon.py +800 -0
  122. mcli/workflow/daemon/async_command_database.py +681 -0
  123. mcli/workflow/daemon/async_process_manager.py +591 -0
  124. mcli/workflow/daemon/client.py +530 -0
  125. mcli/workflow/daemon/commands.py +1196 -0
  126. mcli/workflow/daemon/daemon.py +905 -0
  127. mcli/workflow/daemon/daemon_api.py +59 -0
  128. mcli/workflow/daemon/enhanced_daemon.py +571 -0
  129. mcli/workflow/daemon/process_cli.py +244 -0
  130. mcli/workflow/daemon/process_manager.py +439 -0
  131. mcli/workflow/daemon/test_daemon.py +275 -0
  132. mcli/workflow/dashboard/dashboard_cmd.py +113 -0
  133. mcli/workflow/docker/docker.py +0 -0
  134. mcli/workflow/file/file.py +100 -0
  135. mcli/workflow/gcloud/config.toml +21 -0
  136. mcli/workflow/gcloud/gcloud.py +58 -0
  137. mcli/workflow/git_commit/ai_service.py +328 -0
  138. mcli/workflow/git_commit/commands.py +430 -0
  139. mcli/workflow/lsh_integration.py +355 -0
  140. mcli/workflow/model_service/client.py +594 -0
  141. mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
  142. mcli/workflow/model_service/lightweight_embedder.py +397 -0
  143. mcli/workflow/model_service/lightweight_model_server.py +714 -0
  144. mcli/workflow/model_service/lightweight_test.py +241 -0
  145. mcli/workflow/model_service/model_service.py +1955 -0
  146. mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
  147. mcli/workflow/model_service/pdf_processor.py +386 -0
  148. mcli/workflow/model_service/test_efficient_runner.py +234 -0
  149. mcli/workflow/model_service/test_example.py +315 -0
  150. mcli/workflow/model_service/test_integration.py +131 -0
  151. mcli/workflow/model_service/test_new_features.py +149 -0
  152. mcli/workflow/openai/openai.py +99 -0
  153. mcli/workflow/politician_trading/commands.py +1790 -0
  154. mcli/workflow/politician_trading/config.py +134 -0
  155. mcli/workflow/politician_trading/connectivity.py +490 -0
  156. mcli/workflow/politician_trading/data_sources.py +395 -0
  157. mcli/workflow/politician_trading/database.py +410 -0
  158. mcli/workflow/politician_trading/demo.py +248 -0
  159. mcli/workflow/politician_trading/models.py +165 -0
  160. mcli/workflow/politician_trading/monitoring.py +413 -0
  161. mcli/workflow/politician_trading/scrapers.py +966 -0
  162. mcli/workflow/politician_trading/scrapers_california.py +412 -0
  163. mcli/workflow/politician_trading/scrapers_eu.py +377 -0
  164. mcli/workflow/politician_trading/scrapers_uk.py +350 -0
  165. mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
  166. mcli/workflow/politician_trading/supabase_functions.py +354 -0
  167. mcli/workflow/politician_trading/workflow.py +852 -0
  168. mcli/workflow/registry/registry.py +180 -0
  169. mcli/workflow/repo/repo.py +223 -0
  170. mcli/workflow/scheduler/commands.py +493 -0
  171. mcli/workflow/scheduler/cron_parser.py +238 -0
  172. mcli/workflow/scheduler/job.py +182 -0
  173. mcli/workflow/scheduler/monitor.py +139 -0
  174. mcli/workflow/scheduler/persistence.py +324 -0
  175. mcli/workflow/scheduler/scheduler.py +679 -0
  176. mcli/workflow/sync/sync_cmd.py +437 -0
  177. mcli/workflow/sync/test_cmd.py +314 -0
  178. mcli/workflow/videos/videos.py +242 -0
  179. mcli/workflow/wakatime/wakatime.py +11 -0
  180. mcli/workflow/workflow.py +37 -0
  181. mcli_framework-7.0.0.dist-info/METADATA +479 -0
  182. mcli_framework-7.0.0.dist-info/RECORD +186 -0
  183. mcli_framework-7.0.0.dist-info/WHEEL +5 -0
  184. mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
  185. mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
  186. mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
mcli/app/chat_cmd.py ADDED
@@ -0,0 +1,42 @@
1
+ import click
2
+
3
+ from mcli.chat.chat import ChatClient
4
+ from mcli.chat.enhanced_chat import EnhancedChatClient
5
+
6
+
7
+ @click.command()
8
+ @click.option(
9
+ "--remote", is_flag=True, help="Use remote online models instead of local lightweight models"
10
+ )
11
+ @click.option("--model", "-m", help="Specific model to use (overrides default behavior)")
12
+ @click.option(
13
+ "--enhanced",
14
+ is_flag=True,
15
+ default=True,
16
+ help="Use enhanced chat with RAG-based command search (default: enabled)",
17
+ )
18
+ @click.option("--classic", is_flag=True, help="Use classic chat interface without command search")
19
+ def chat(remote: bool, model: str, enhanced: bool, classic: bool):
20
+ """Start an interactive chat session with the MCLI Chat Assistant.
21
+
22
+ 🤖 Enhanced Mode (Default):
23
+ - Self-referential command discovery and suggestions
24
+ - RAG-based semantic search of available MCLI commands
25
+ - Intelligent intent analysis and contextual recommendations
26
+ - Real-time system status awareness
27
+
28
+ 💬 Classic Mode:
29
+ - Traditional chat interface
30
+ - Basic system integration
31
+
32
+ By default, uses lightweight local models for privacy and speed.
33
+ Use --remote to connect to online models like OpenAI or Anthropic.
34
+ """
35
+ # Choose chat client based on options
36
+ if classic:
37
+ client = ChatClient(use_remote=remote, model_override=model)
38
+ client.start_interactive_session()
39
+ else:
40
+ # Use enhanced client by default
41
+ client = EnhancedChatClient(use_remote=remote, model_override=model)
42
+ client.start_interactive_session()
@@ -0,0 +1,226 @@
1
+ import json
2
+ from typing import Optional
3
+
4
+ import click
5
+
6
+ from mcli.lib.api.daemon_client import get_daemon_client
7
+ from mcli.lib.discovery.command_discovery import get_command_discovery
8
+ from mcli.lib.ui.styling import console
9
+
10
+
11
+ @click.group()
12
+ def commands():
13
+ """Manage and execute available commands."""
14
+ pass
15
+
16
+
17
+ @commands.command("list")
18
+ @click.option("--include-groups", is_flag=True, help="Include command groups in listing")
19
+ @click.option("--daemon-only", is_flag=True, help="Show only daemon database commands")
20
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
21
+ def list_commands(include_groups: bool, daemon_only: bool, as_json: bool):
22
+ """List all available commands"""
23
+ try:
24
+ if daemon_only:
25
+ # Show only daemon database commands
26
+ client = get_daemon_client()
27
+ result = client.list_commands(all=True)
28
+
29
+ if isinstance(result, dict):
30
+ commands_data = result.get("commands", [])
31
+ elif isinstance(result, list):
32
+ commands_data = result
33
+ else:
34
+ commands_data = []
35
+ else:
36
+ # Show all discovered Click commands
37
+ discovery = get_command_discovery()
38
+ commands_data = discovery.get_commands(include_groups=include_groups)
39
+
40
+ if as_json:
41
+ click.echo(
42
+ json.dumps({"commands": commands_data, "total": len(commands_data)}, indent=2)
43
+ )
44
+ return
45
+
46
+ if not commands_data:
47
+ console.print("No commands found")
48
+ return
49
+
50
+ console.print(f"[bold]Available Commands ({len(commands_data)}):[/bold]")
51
+ for cmd in commands_data:
52
+ # Handle different command sources
53
+ if daemon_only:
54
+ status = "[red][INACTIVE][/red] " if not cmd.get("is_active", True) else ""
55
+ console.print(
56
+ f"{status}• [green]{cmd['name']}[/green] ({cmd.get('language', 'python')})"
57
+ )
58
+ else:
59
+ group_indicator = "[blue][GROUP][/blue] " if cmd.get("is_group") else ""
60
+ console.print(f"{group_indicator}• [green]{cmd['full_name']}[/green]")
61
+
62
+ if cmd.get("description"):
63
+ console.print(f" {cmd['description']}")
64
+ if cmd.get("module"):
65
+ console.print(f" Module: {cmd['module']}")
66
+ if cmd.get("tags"):
67
+ console.print(f" Tags: {', '.join(cmd['tags'])}")
68
+ console.print()
69
+
70
+ except Exception as e:
71
+ console.print(f"[red]Error: {e}[/red]")
72
+
73
+
74
+ @commands.command("search")
75
+ @click.argument("query")
76
+ @click.option("--daemon-only", is_flag=True, help="Search only daemon database commands")
77
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
78
+ def search_commands(query: str, daemon_only: bool, as_json: bool):
79
+ """Search commands by name, description, or tags"""
80
+ try:
81
+ if daemon_only:
82
+ # Search only daemon database commands
83
+ client = get_daemon_client()
84
+ result = client.list_commands(all=True)
85
+
86
+ if isinstance(result, dict):
87
+ all_commands = result.get("commands", [])
88
+ elif isinstance(result, list):
89
+ all_commands = result
90
+ else:
91
+ all_commands = []
92
+
93
+ # Filter commands that match the query
94
+ matching_commands = [
95
+ cmd
96
+ for cmd in all_commands
97
+ if (
98
+ query.lower() in cmd["name"].lower()
99
+ or query.lower() in (cmd["description"] or "").lower()
100
+ or any(query.lower() in tag.lower() for tag in cmd.get("tags", []))
101
+ )
102
+ ]
103
+ else:
104
+ # Search all discovered Click commands
105
+ discovery = get_command_discovery()
106
+ matching_commands = discovery.search_commands(query)
107
+
108
+ if as_json:
109
+ click.echo(
110
+ json.dumps(
111
+ {
112
+ "commands": matching_commands,
113
+ "total": len(matching_commands),
114
+ "query": query,
115
+ },
116
+ indent=2,
117
+ )
118
+ )
119
+ return
120
+
121
+ if not matching_commands:
122
+ console.print(f"No commands found matching '[yellow]{query}[/yellow]'")
123
+ return
124
+
125
+ console.print(f"[bold]Commands matching '{query}' ({len(matching_commands)}):[/bold]")
126
+ for cmd in matching_commands:
127
+ if daemon_only:
128
+ status = "[red][INACTIVE][/red] " if not cmd.get("is_active", True) else ""
129
+ console.print(
130
+ f"{status}• [green]{cmd['name']}[/green] ({cmd.get('language', 'python')})"
131
+ )
132
+ else:
133
+ group_indicator = "[blue][GROUP][/blue] " if cmd.get("is_group") else ""
134
+ console.print(f"{group_indicator}• [green]{cmd['full_name']}[/green]")
135
+
136
+ console.print(f" [italic]{cmd['description']}[/italic]")
137
+ if cmd.get("module"):
138
+ console.print(f" Module: {cmd['module']}")
139
+ console.print()
140
+
141
+ except Exception as e:
142
+ console.print(f"[red]Error: {e}[/red]")
143
+
144
+
145
+ @commands.command("execute")
146
+ @click.argument("command_name")
147
+ @click.argument("args", nargs=-1)
148
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
149
+ @click.option("--timeout", type=int, help="Execution timeout in seconds")
150
+ def execute_command(command_name: str, args: tuple, as_json: bool, timeout: Optional[int]):
151
+ """Execute a command by name"""
152
+ try:
153
+ client = get_daemon_client()
154
+ result = client.execute_command(command_name=command_name, args=list(args), timeout=timeout)
155
+
156
+ if as_json:
157
+ click.echo(json.dumps(result, indent=2))
158
+ return
159
+
160
+ if result.get("success"):
161
+ if result.get("output"):
162
+ console.print(f"[green]Output:[/green]\n{result['output']}")
163
+ else:
164
+ console.print("[green]Command executed successfully[/green]")
165
+
166
+ if result.get("execution_time_ms"):
167
+ console.print(f"[dim]Execution time: {result['execution_time_ms']}ms[/dim]")
168
+ else:
169
+ console.print(f"[red]Error:[/red] {result.get('error', 'Unknown error')}")
170
+
171
+ except Exception as e:
172
+ console.print(f"[red]Error: {e}[/red]")
173
+
174
+
175
+ @commands.command("info")
176
+ @click.argument("command_name")
177
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
178
+ def command_info(command_name: str, as_json: bool):
179
+ """Show detailed information about a command"""
180
+ try:
181
+ client = get_daemon_client()
182
+ result = client.list_commands(all=True)
183
+
184
+ if isinstance(result, dict):
185
+ all_commands = result.get("commands", [])
186
+ elif isinstance(result, list):
187
+ all_commands = result
188
+ else:
189
+ all_commands = []
190
+
191
+ # Find the command
192
+ command = None
193
+ for cmd in all_commands:
194
+ if cmd["name"].lower() == command_name.lower():
195
+ command = cmd
196
+ break
197
+
198
+ if not command:
199
+ console.print(f"[red]Command '{command_name}' not found[/red]")
200
+ return
201
+
202
+ if as_json:
203
+ click.echo(json.dumps(command, indent=2))
204
+ return
205
+
206
+ console.print(f"[bold]Command: {command['name']}[/bold]")
207
+ console.print(f"Language: {command['language']}")
208
+ console.print(f"Description: {command.get('description', 'No description')}")
209
+ console.print(f"Group: {command.get('group', 'None')}")
210
+ console.print(f"Tags: {', '.join(command.get('tags', []))}")
211
+ console.print(f"Active: {'Yes' if command.get('is_active', True) else 'No'}")
212
+ console.print(f"Execution Count: {command.get('execution_count', 0)}")
213
+
214
+ if command.get("created_at"):
215
+ console.print(f"Created: {command['created_at']}")
216
+ if command.get("last_executed"):
217
+ console.print(f"Last Executed: {command['last_executed']}")
218
+
219
+ if command.get("code"):
220
+ console.print(f"\n[bold]Code:[/bold]")
221
+ console.print(f"```{command['language']}")
222
+ console.print(command["code"])
223
+ console.print("```")
224
+
225
+ except Exception as e:
226
+ console.print(f"[red]Error: {e}[/red]")
@@ -0,0 +1,216 @@
1
+ """
2
+ Shell completion commands for MCLI
3
+
4
+ Provides commands to generate and install shell completion scripts
5
+ for bash, zsh, and fish shells.
6
+ """
7
+
8
+ import os
9
+ import click
10
+ from pathlib import Path
11
+ from mcli.lib.ui.styling import success
12
+
13
+
14
+ @click.group(name="completion")
15
+ def completion():
16
+ """Shell completion utilities"""
17
+ pass
18
+
19
+
20
+ @completion.command(name="bash")
21
+ @click.pass_context
22
+ def bash_completion(ctx):
23
+ """Generate bash completion script"""
24
+ from click.shell_completion import BashComplete
25
+
26
+ # Get the root CLI app
27
+ app = ctx.find_root().command
28
+ complete = BashComplete(app, {}, "mcli", "complete")
29
+ script = complete.source()
30
+
31
+ click.echo("# Bash completion script for MCLI")
32
+ click.echo("# Add this to your ~/.bashrc or ~/.bash_profile:")
33
+ click.echo()
34
+ click.echo(script)
35
+
36
+
37
+ @completion.command(name="zsh")
38
+ @click.pass_context
39
+ def zsh_completion(ctx):
40
+ """Generate zsh completion script"""
41
+ from click.shell_completion import ZshComplete
42
+
43
+ # Get the root CLI app
44
+ app = ctx.find_root().command
45
+ complete = ZshComplete(app, {}, "mcli", "complete")
46
+ script = complete.source()
47
+
48
+ click.echo("# Zsh completion script for MCLI")
49
+ click.echo("# Add this to your ~/.zshrc:")
50
+ click.echo()
51
+ click.echo(script)
52
+
53
+
54
+ @completion.command(name="fish")
55
+ @click.pass_context
56
+ def fish_completion(ctx):
57
+ """Generate fish completion script"""
58
+ from click.shell_completion import FishComplete
59
+
60
+ # Get the root CLI app
61
+ app = ctx.find_root().command
62
+ complete = FishComplete(app, {}, "mcli", "complete")
63
+ script = complete.source()
64
+
65
+ click.echo("# Fish completion script for MCLI")
66
+ click.echo("# Add this to ~/.config/fish/completions/mcli.fish:")
67
+ click.echo()
68
+ click.echo(script)
69
+
70
+
71
+ @completion.command(name="install")
72
+ @click.option("--shell", type=click.Choice(['bash', 'zsh', 'fish']),
73
+ help="Shell to install for (auto-detected if not specified)")
74
+ @click.pass_context
75
+ def install_completion(ctx, shell):
76
+ """Install shell completion for the current user"""
77
+ import subprocess
78
+
79
+ # Auto-detect shell if not specified
80
+ if not shell:
81
+ shell_path = os.environ.get('SHELL', '')
82
+ if 'bash' in shell_path:
83
+ shell = 'bash'
84
+ elif 'zsh' in shell_path:
85
+ shell = 'zsh'
86
+ elif 'fish' in shell_path:
87
+ shell = 'fish'
88
+ else:
89
+ click.echo("❌ Could not auto-detect shell. Please specify --shell")
90
+ return
91
+
92
+ # Get the root CLI app
93
+ app = ctx.find_root().command
94
+
95
+ try:
96
+ if shell == 'bash':
97
+ from click.shell_completion import BashComplete
98
+ complete = BashComplete(app, {}, "mcli", "complete")
99
+ script = complete.source()
100
+
101
+ # Install to bash completion directory
102
+ bash_completion_dir = Path.home() / ".bash_completion.d"
103
+ bash_completion_dir.mkdir(exist_ok=True)
104
+ completion_file = bash_completion_dir / "mcli"
105
+ completion_file.write_text(script)
106
+
107
+ # Add sourcing to .bashrc if needed
108
+ bashrc = Path.home() / ".bashrc"
109
+ source_line = f"[ -f {completion_file} ] && source {completion_file}"
110
+
111
+ if bashrc.exists():
112
+ content = bashrc.read_text()
113
+ if source_line not in content:
114
+ with bashrc.open("a") as f:
115
+ f.write(f"\n# MCLI completion\n{source_line}\n")
116
+ click.echo("✅ Added completion sourcing to ~/.bashrc")
117
+ else:
118
+ click.echo("ℹ️ Completion already configured in ~/.bashrc")
119
+ else:
120
+ click.echo(f"✅ Completion installed to {completion_file}")
121
+ click.echo("💡 Add this to your ~/.bashrc:")
122
+ click.echo(source_line)
123
+
124
+ elif shell == 'zsh':
125
+ from click.shell_completion import ZshComplete
126
+ complete = ZshComplete(app, {}, "mcli", "complete")
127
+ script = complete.source()
128
+
129
+ # Install to zsh completion directory
130
+ zsh_completion_dir = Path.home() / ".config" / "zsh" / "completions"
131
+ zsh_completion_dir.mkdir(parents=True, exist_ok=True)
132
+ completion_file = zsh_completion_dir / "_mcli"
133
+ completion_file.write_text(script)
134
+
135
+ # Add to fpath in .zshrc if needed
136
+ zshrc = Path.home() / ".zshrc"
137
+ fpath_line = f'fpath=("{zsh_completion_dir}" $fpath)'
138
+
139
+ if zshrc.exists():
140
+ content = zshrc.read_text()
141
+ if str(zsh_completion_dir) not in content:
142
+ with zshrc.open("a") as f:
143
+ f.write(f"\n# MCLI completion\n{fpath_line}\nautoload -U compinit && compinit\n")
144
+ click.echo("✅ Added completion to ~/.zshrc")
145
+ else:
146
+ click.echo("ℹ️ Completion already configured in ~/.zshrc")
147
+ else:
148
+ click.echo(f"✅ Completion installed to {completion_file}")
149
+ click.echo("💡 Add this to your ~/.zshrc:")
150
+ click.echo(f"{fpath_line}\nautoload -U compinit && compinit")
151
+
152
+ elif shell == 'fish':
153
+ from click.shell_completion import FishComplete
154
+ complete = FishComplete(app, {}, "mcli", "complete")
155
+ script = complete.source()
156
+
157
+ # Install to fish completion directory
158
+ fish_completion_dir = Path.home() / ".config" / "fish" / "completions"
159
+ fish_completion_dir.mkdir(parents=True, exist_ok=True)
160
+ completion_file = fish_completion_dir / "mcli.fish"
161
+ completion_file.write_text(script)
162
+ click.echo(f"✅ Completion installed to {completion_file}")
163
+
164
+ click.echo(f"🎉 Shell completion for {shell} installed successfully!")
165
+ click.echo("💡 Restart your shell or source your profile to enable completions")
166
+
167
+ except Exception as e:
168
+ click.echo(f"❌ Failed to install completion: {e}")
169
+
170
+
171
+ @completion.command(name="status")
172
+ def completion_status():
173
+ """Check current shell completion status"""
174
+ current_shell = os.environ.get('SHELL', 'unknown')
175
+ shell_name = Path(current_shell).name if current_shell != 'unknown' else 'unknown'
176
+
177
+ click.echo(f"🐚 Current shell: {shell_name} ({current_shell})")
178
+ click.echo()
179
+
180
+ # Check for existing completions
181
+ completions_found = []
182
+
183
+ # Check bash
184
+ bash_completion = Path.home() / ".bash_completion.d" / "mcli"
185
+ if bash_completion.exists():
186
+ completions_found.append(f"✅ Bash completion: {bash_completion}")
187
+ else:
188
+ completions_found.append("❌ Bash completion: Not installed")
189
+
190
+ # Check zsh
191
+ zsh_completion = Path.home() / ".config" / "zsh" / "completions" / "_mcli"
192
+ if zsh_completion.exists():
193
+ completions_found.append(f"✅ Zsh completion: {zsh_completion}")
194
+ else:
195
+ completions_found.append("❌ Zsh completion: Not installed")
196
+
197
+ # Check fish
198
+ fish_completion = Path.home() / ".config" / "fish" / "completions" / "mcli.fish"
199
+ if fish_completion.exists():
200
+ completions_found.append(f"✅ Fish completion: {fish_completion}")
201
+ else:
202
+ completions_found.append("❌ Fish completion: Not installed")
203
+
204
+ for status in completions_found:
205
+ click.echo(status)
206
+
207
+ click.echo()
208
+ click.echo("💡 To install completion for your shell:")
209
+ click.echo(" mcli completion install")
210
+ click.echo()
211
+ click.echo("💡 To generate completion script manually:")
212
+ click.echo(f" mcli completion {shell_name}")
213
+
214
+
215
+ # Export the CLI group for registration
216
+ cli = completion