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
@@ -0,0 +1,530 @@
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ import tempfile
6
+ import uuid
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ import click
12
+
13
+ # Import the daemon classes
14
+ from .commands import Command, CommandDatabase, CommandExecutor
15
+
16
+
17
+ @click.group()
18
+ def client():
19
+ """Client CLI for daemon commands."""
20
+ pass
21
+
22
+
23
+ class DaemonClient:
24
+ """Client interface for interacting with the daemon service"""
25
+
26
+ def __init__(self, timeout: int = 30):
27
+ self.db = CommandDatabase()
28
+ self.executor = CommandExecutor()
29
+ self.timeout = timeout # Default timeout for operations
30
+ self._validate_connection()
31
+
32
+ def _validate_connection(self):
33
+ """Verify connection to the daemon service"""
34
+ try:
35
+ self.db.get_all_commands() # Simple query to test connection
36
+ except Exception as e:
37
+ raise ConnectionError(f"Failed to connect to daemon: {str(e)}") from e
38
+
39
+ def add_command_from_file(
40
+ self,
41
+ name: str,
42
+ file_path: str,
43
+ description: Optional[str] = None,
44
+ language: str = "python",
45
+ group: Optional[str] = None,
46
+ tags: Optional[List[str]] = None,
47
+ ) -> str:
48
+ """Add a command from a file"""
49
+ # Read the file
50
+ with open(file_path, "r") as f:
51
+ code = f.read()
52
+
53
+ # Detect language from file extension if not specified
54
+ if language == "auto":
55
+ ext = Path(file_path).suffix.lower()
56
+ language_map = {
57
+ ".py": "python",
58
+ ".js": "node",
59
+ ".lua": "lua",
60
+ ".sh": "shell",
61
+ ".bash": "shell",
62
+ }
63
+ language = language_map.get(ext, "python")
64
+
65
+ # Create command
66
+ command = Command(
67
+ id=str(uuid.uuid4()),
68
+ name=name,
69
+ description=description or f"Command from {file_path}",
70
+ code=code,
71
+ language=language,
72
+ group=group,
73
+ tags=tags or [],
74
+ )
75
+
76
+ # Add to database
77
+ return self.db.add_command(command)
78
+
79
+ def add_command_from_stdin(
80
+ self,
81
+ name: str,
82
+ description: Optional[str] = None,
83
+ language: str = "python",
84
+ group: Optional[str] = None,
85
+ tags: Optional[List[str]] = None,
86
+ ) -> str:
87
+ """Add a command from stdin"""
88
+ click.echo("Enter your code (Ctrl+D when done):")
89
+
90
+ # Read from stdin
91
+ lines = []
92
+ try:
93
+ while True:
94
+ line = input()
95
+ lines.append(line)
96
+ except EOFError:
97
+ pass
98
+
99
+ code = "\n".join(lines)
100
+
101
+ if not code.strip():
102
+ raise ValueError("No code provided")
103
+
104
+ # Create command
105
+ command = Command(
106
+ id=str(uuid.uuid4()),
107
+ name=name,
108
+ description=description or f"Command from stdin",
109
+ code=code,
110
+ language=language,
111
+ group=group,
112
+ tags=tags or [],
113
+ )
114
+
115
+ # Add to database
116
+ return self.db.add_command(command)
117
+
118
+ def add_command_interactive(self) -> Optional[str]:
119
+ """Interactive command creation"""
120
+ # Get command name
121
+ name = click.prompt("Command name", type=str)
122
+
123
+ # Check if name already exists
124
+ existing = self.db.search_commands(name, limit=1)
125
+ if existing and existing[0].name == name:
126
+ if not click.confirm(f"Command '{name}' already exists. Overwrite?"):
127
+ return None
128
+
129
+ # Get description
130
+ description = click.prompt("Description (optional)", type=str, default="")
131
+
132
+ # Get language
133
+ language = click.prompt(
134
+ "Language", type=click.Choice(["python", "node", "lua", "shell"]), default="python"
135
+ )
136
+
137
+ # Get group
138
+ group = click.prompt("Group (optional)", type=str, default="")
139
+ if not group:
140
+ group = None
141
+
142
+ # Get tags
143
+ tags_input = click.prompt("Tags (comma-separated, optional)", type=str, default="")
144
+ tags = [tag.strip() for tag in tags_input.split(",")] if tags_input else []
145
+
146
+ # Get code source
147
+ source = click.prompt(
148
+ "Code source", type=click.Choice(["file", "stdin", "paste"]), default="paste"
149
+ )
150
+
151
+ if source == "file":
152
+ file_path = click.prompt("File path", type=click.Path(exists=True))
153
+ return self.add_command_from_file(name, file_path, description, language, group, tags)
154
+ elif source == "stdin":
155
+ return self.add_command_from_stdin(name, description, language, group, tags)
156
+ else: # paste
157
+ click.echo("Paste your code below (Ctrl+D when done):")
158
+ lines = []
159
+ try:
160
+ while True:
161
+ line = input()
162
+ lines.append(line)
163
+ except EOFError:
164
+ pass
165
+
166
+ code = "\n".join(lines)
167
+
168
+ if not code.strip():
169
+ raise ValueError("No code provided")
170
+
171
+ # Create command
172
+ command = Command(
173
+ id=str(uuid.uuid4()),
174
+ name=name,
175
+ description=description or f"Command from paste",
176
+ code=code,
177
+ language=language,
178
+ group=group,
179
+ tags=tags,
180
+ )
181
+
182
+ # Add to database
183
+ return self.db.add_command(command)
184
+
185
+ def execute_command(self, command_id: str, args: List[str] = None) -> Dict[str, Any]:
186
+ """Execute a command"""
187
+ command = self.db.get_command(command_id)
188
+ if not command:
189
+ raise ValueError(f"Command '{command_id}' not found")
190
+
191
+ # Execute
192
+ result = self.executor.execute_command(command, args or [])
193
+
194
+ # Record execution
195
+ self.db.record_execution(
196
+ command_id=command_id,
197
+ status=result["status"],
198
+ output=result.get("output", ""),
199
+ error=result.get("error", ""),
200
+ execution_time_ms=result.get("execution_time_ms", 0),
201
+ )
202
+
203
+ return result
204
+
205
+ def search_commands(self, query: str, limit: int = 10) -> List[Command]:
206
+ """Search for commands"""
207
+ return self.db.search_commands(query, limit)
208
+
209
+ def find_similar_commands(self, query: str, limit: int = 5) -> List[tuple]:
210
+ """Find similar commands using cosine similarity"""
211
+ return self.db.find_similar_commands(query, limit)
212
+
213
+ def get_all_commands(self) -> List[Command]:
214
+ """Get all commands"""
215
+ return self.db.get_all_commands()
216
+
217
+ def get_command(self, command_id: str) -> Optional[Command]:
218
+ """Get a command by ID"""
219
+ return self.db.get_command(command_id)
220
+
221
+ def update_command(self, command: Command) -> bool:
222
+ """Update a command"""
223
+ return self.db.update_command(command)
224
+
225
+ def delete_command(self, command_id: str) -> bool:
226
+ """Delete a command"""
227
+ return self.db.delete_command(command_id)
228
+
229
+
230
+ # CLI Commands - these will be subcommands under the daemon group
231
+ @client.command()
232
+ @click.argument("name")
233
+ @click.argument("file_path", type=click.Path(exists=True))
234
+ @click.option("--description", help="Command description")
235
+ @click.option(
236
+ "--language",
237
+ type=click.Choice(["python", "node", "lua", "shell", "auto"]),
238
+ default="auto",
239
+ help="Programming language",
240
+ )
241
+ @click.option("--group", help="Command group")
242
+ @click.option("--tags", help="Comma-separated tags")
243
+ def add_file(name: str, file_path: str, description: str, language: str, group: str, tags: str):
244
+ """Add a command from a file"""
245
+ client = DaemonClient()
246
+
247
+ # Parse tags
248
+ tag_list = [tag.strip() for tag in tags.split(",")] if tags else []
249
+
250
+ try:
251
+ command_id = client.add_command_from_file(
252
+ name=name,
253
+ file_path=file_path,
254
+ description=description,
255
+ language=language,
256
+ group=group,
257
+ tags=tag_list,
258
+ )
259
+ click.echo(f"✅ Command '{name}' added with ID: {command_id}")
260
+ except Exception as e:
261
+ click.echo(f"❌ Error adding command: {e}", err=True)
262
+
263
+
264
+ @client.command()
265
+ @click.argument("name")
266
+ @click.option("--description", help="Command description")
267
+ @click.option(
268
+ "--language",
269
+ type=click.Choice(["python", "node", "lua", "shell"]),
270
+ default="python",
271
+ help="Programming language",
272
+ )
273
+ @click.option("--group", help="Command group")
274
+ @click.option("--tags", help="Comma-separated tags")
275
+ def add_stdin(name: str, description: str, language: str, group: str, tags: str):
276
+ """Add a command from stdin"""
277
+ client = DaemonClient()
278
+
279
+ # Parse tags
280
+ tag_list = [tag.strip() for tag in tags.split(",")] if tags else []
281
+
282
+ try:
283
+ command_id = client.add_command_from_stdin(
284
+ name=name, description=description, language=language, group=group, tags=tag_list
285
+ )
286
+ click.echo(f"✅ Command '{name}' added with ID: {command_id}")
287
+ except Exception as e:
288
+ click.echo(f"❌ Error adding command: {e}", err=True)
289
+
290
+
291
+ @client.command()
292
+ def add_interactive():
293
+ """Add a command interactively"""
294
+ client = DaemonClient()
295
+
296
+ try:
297
+ command_id = client.add_command_interactive()
298
+ if command_id:
299
+ click.echo(f"✅ Command added with ID: {command_id}")
300
+ else:
301
+ click.echo("Command creation cancelled")
302
+ except Exception as e:
303
+ click.echo(f"❌ Error adding command: {e}", err=True)
304
+
305
+
306
+ @client.command()
307
+ @click.argument("command_id")
308
+ @click.argument("args", nargs=-1)
309
+ def execute(command_id: str, args: List[str]):
310
+ """Execute a command"""
311
+ client = DaemonClient()
312
+
313
+ try:
314
+ result = client.execute_command(command_id, list(args))
315
+
316
+ if result["success"]:
317
+ click.echo("✅ Command executed successfully")
318
+ if result["output"]:
319
+ click.echo("Output:")
320
+ click.echo(result["output"])
321
+ else:
322
+ click.echo("❌ Command execution failed")
323
+ if result["error"]:
324
+ click.echo(f"Error: {result['error']}")
325
+
326
+ click.echo(f"Execution time: {result.get('execution_time_ms', 0)}ms")
327
+
328
+ except Exception as e:
329
+ click.echo(f"❌ Error executing command: {e}", err=True)
330
+
331
+
332
+ @client.command()
333
+ @click.argument("query")
334
+ @click.option("--limit", default=10, help="Maximum number of results")
335
+ @click.option("--similar", is_flag=True, help="Use similarity search")
336
+ def search(query: str, limit: int, similar: bool):
337
+ """Search for commands"""
338
+ client = DaemonClient()
339
+
340
+ try:
341
+ if similar:
342
+ results = client.find_similar_commands(query, limit)
343
+ if results:
344
+ click.echo(f"Found {len(results)} similar command(s):")
345
+ for cmd, similarity in results:
346
+ click.echo(f" {cmd.name} ({cmd.language}) - {cmd.description}")
347
+ click.echo(f" Similarity: {similarity:.3f}")
348
+ if cmd.tags:
349
+ click.echo(f" Tags: {', '.join(cmd.tags)}")
350
+ click.echo()
351
+ else:
352
+ click.echo("No similar commands found")
353
+ else:
354
+ commands = client.search_commands(query, limit)
355
+ if commands:
356
+ click.echo(f"Found {len(commands)} command(s):")
357
+ for cmd in commands:
358
+ click.echo(f" {cmd.name} ({cmd.language}) - {cmd.description}")
359
+ if cmd.tags:
360
+ click.echo(f" Tags: {', '.join(cmd.tags)}")
361
+ click.echo()
362
+ else:
363
+ click.echo("No commands found")
364
+
365
+ except Exception as e:
366
+ click.echo(f"❌ Error searching commands: {e}", err=True)
367
+
368
+
369
+ @client.command()
370
+ @click.option("--group", help="Filter by group")
371
+ @click.option("--language", help="Filter by language")
372
+ def list(group: str, language: str):
373
+ """List all commands"""
374
+ client = DaemonClient()
375
+
376
+ try:
377
+ commands = client.get_all_commands()
378
+
379
+ # Apply filters
380
+ if group:
381
+ commands = [cmd for cmd in commands if cmd.group == group]
382
+ if language:
383
+ commands = [cmd for cmd in commands if cmd.language == language]
384
+
385
+ if not commands:
386
+ click.echo("No commands found")
387
+ return
388
+
389
+ click.echo(f"Found {len(commands)} command(s):")
390
+ for cmd in commands:
391
+ click.echo(f" {cmd.name} ({cmd.language}) - {cmd.description}")
392
+ if cmd.group:
393
+ click.echo(f" Group: {cmd.group}")
394
+ if cmd.tags:
395
+ click.echo(f" Tags: {', '.join(cmd.tags)}")
396
+ click.echo(f" Executed {cmd.execution_count} times")
397
+ click.echo()
398
+
399
+ except Exception as e:
400
+ click.echo(f"❌ Error listing commands: {e}", err=True)
401
+
402
+
403
+ @client.command()
404
+ @click.argument("command_id")
405
+ def show(command_id: str):
406
+ """Show command details"""
407
+ client = DaemonClient()
408
+
409
+ try:
410
+ command = client.get_command(command_id)
411
+ if not command:
412
+ click.echo(f"Command '{command_id}' not found")
413
+ return
414
+
415
+ click.echo(f"Command: {command.name}")
416
+ click.echo(f"ID: {command.id}")
417
+ click.echo(f"Description: {command.description}")
418
+ click.echo(f"Language: {command.language}")
419
+ if command.group:
420
+ click.echo(f"Group: {command.group}")
421
+ if command.tags:
422
+ click.echo(f"Tags: {', '.join(command.tags)}")
423
+ click.echo(f"Created: {command.created_at}")
424
+ click.echo(f"Updated: {command.updated_at}")
425
+ click.echo(f"Executed: {command.execution_count} times")
426
+ if command.last_executed:
427
+ click.echo(f"Last executed: {command.last_executed}")
428
+ click.echo()
429
+ click.echo("Code:")
430
+ click.echo("=" * 50)
431
+ click.echo(command.code)
432
+ click.echo("=" * 50)
433
+
434
+ except Exception as e:
435
+ click.echo(f"❌ Error showing command: {e}", err=True)
436
+
437
+
438
+ @client.command()
439
+ @click.argument("command_id")
440
+ def delete(command_id: str):
441
+ """Delete a command"""
442
+ client = DaemonClient()
443
+
444
+ try:
445
+ command = client.get_command(command_id)
446
+ if not command:
447
+ click.echo(f"Command '{command_id}' not found")
448
+ return
449
+
450
+ if click.confirm(f"Are you sure you want to delete command '{command.name}'?"):
451
+ if client.delete_command(command_id):
452
+ click.echo(f"✅ Command '{command.name}' deleted")
453
+ else:
454
+ click.echo(f"❌ Error deleting command '{command.name}'")
455
+ else:
456
+ click.echo("Deletion cancelled")
457
+
458
+ except Exception as e:
459
+ click.echo(f"❌ Error deleting command: {e}", err=True)
460
+
461
+
462
+ @client.command()
463
+ @click.argument("command_id")
464
+ @click.option("--name", help="New name")
465
+ @click.option("--description", help="New description")
466
+ @click.option("--group", help="New group")
467
+ @click.option("--tags", help="New tags (comma-separated)")
468
+ def edit(command_id: str, name: str, description: str, group: str, tags: str):
469
+ """Edit a command"""
470
+ client = DaemonClient()
471
+
472
+ try:
473
+ command = client.get_command(command_id)
474
+ if not command:
475
+ click.echo(f"Command '{command_id}' not found")
476
+ return
477
+
478
+ # Update fields if provided
479
+ if name:
480
+ command.name = name
481
+ if description:
482
+ command.description = description
483
+ if group:
484
+ command.group = group
485
+ if tags:
486
+ command.tags = [tag.strip() for tag in tags.split(",")]
487
+
488
+ command.updated_at = datetime.now()
489
+
490
+ if client.update_command(command):
491
+ click.echo(f"✅ Command '{command.name}' updated")
492
+ else:
493
+ click.echo(f"❌ Error updating command '{command.name}'")
494
+
495
+ except Exception as e:
496
+ click.echo(f"❌ Error editing command: {e}", err=True)
497
+
498
+
499
+ @client.command()
500
+ def groups():
501
+ """List all command groups"""
502
+ client = DaemonClient()
503
+
504
+ try:
505
+ commands = client.get_all_commands()
506
+ groups = {}
507
+
508
+ for cmd in commands:
509
+ group = cmd.group or "ungrouped"
510
+ if group not in groups:
511
+ groups[group] = []
512
+ groups[group].append(cmd)
513
+
514
+ if not groups:
515
+ click.echo("No groups found")
516
+ return
517
+
518
+ click.echo("Command groups:")
519
+ for group_name, group_commands in groups.items():
520
+ click.echo(f" {group_name} ({len(group_commands)} commands)")
521
+ for cmd in group_commands:
522
+ click.echo(f" - {cmd.name} ({cmd.language})")
523
+ click.echo()
524
+
525
+ except Exception as e:
526
+ click.echo(f"❌ Error listing groups: {e}", err=True)
527
+
528
+
529
+ if __name__ == "__main__":
530
+ client()