mcli-framework 7.8.2__py3-none-any.whl → 7.8.4__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 (79) hide show
  1. mcli/app/commands_cmd.py +942 -199
  2. mcli/ml/dashboard/app_supabase.py +58 -12
  3. mcli/ml/dashboard/pages/predictions_enhanced.py +82 -38
  4. mcli/ml/dashboard/utils.py +39 -11
  5. mcli/self/self_cmd.py +5 -246
  6. mcli/self/store_cmd.py +424 -0
  7. mcli/test/test_cmd.py +0 -10
  8. mcli/workflow/dashboard/dashboard_cmd.py +180 -0
  9. {mcli_framework-7.8.2.dist-info → mcli_framework-7.8.4.dist-info}/METADATA +1 -1
  10. {mcli_framework-7.8.2.dist-info → mcli_framework-7.8.4.dist-info}/RECORD +14 -77
  11. mcli/__init__.py +0 -160
  12. mcli/__main__.py +0 -14
  13. mcli/app/__init__.py +0 -23
  14. mcli/app/model/__init__.py +0 -0
  15. mcli/app/video/__init__.py +0 -5
  16. mcli/chat/__init__.py +0 -34
  17. mcli/lib/__init__.py +0 -0
  18. mcli/lib/api/__init__.py +0 -0
  19. mcli/lib/auth/__init__.py +0 -1
  20. mcli/lib/config/__init__.py +0 -1
  21. mcli/lib/erd/__init__.py +0 -25
  22. mcli/lib/files/__init__.py +0 -0
  23. mcli/lib/fs/__init__.py +0 -1
  24. mcli/lib/logger/__init__.py +0 -3
  25. mcli/lib/performance/__init__.py +0 -17
  26. mcli/lib/pickles/__init__.py +0 -1
  27. mcli/lib/shell/__init__.py +0 -0
  28. mcli/lib/toml/__init__.py +0 -1
  29. mcli/lib/watcher/__init__.py +0 -0
  30. mcli/ml/__init__.py +0 -16
  31. mcli/ml/api/__init__.py +0 -30
  32. mcli/ml/api/routers/__init__.py +0 -27
  33. mcli/ml/auth/__init__.py +0 -41
  34. mcli/ml/backtesting/__init__.py +0 -33
  35. mcli/ml/cli/__init__.py +0 -5
  36. mcli/ml/config/__init__.py +0 -33
  37. mcli/ml/configs/__init__.py +0 -16
  38. mcli/ml/dashboard/__init__.py +0 -12
  39. mcli/ml/dashboard/components/__init__.py +0 -7
  40. mcli/ml/dashboard/pages/__init__.py +0 -6
  41. mcli/ml/data_ingestion/__init__.py +0 -29
  42. mcli/ml/database/__init__.py +0 -40
  43. mcli/ml/experimentation/__init__.py +0 -29
  44. mcli/ml/features/__init__.py +0 -39
  45. mcli/ml/mlops/__init__.py +0 -19
  46. mcli/ml/models/__init__.py +0 -90
  47. mcli/ml/monitoring/__init__.py +0 -25
  48. mcli/ml/optimization/__init__.py +0 -27
  49. mcli/ml/predictions/__init__.py +0 -5
  50. mcli/ml/preprocessing/__init__.py +0 -24
  51. mcli/ml/scripts/__init__.py +0 -1
  52. mcli/ml/trading/__init__.py +0 -63
  53. mcli/ml/training/__init__.py +0 -7
  54. mcli/mygroup/__init__.py +0 -3
  55. mcli/public/__init__.py +0 -1
  56. mcli/public/commands/__init__.py +0 -2
  57. mcli/self/__init__.py +0 -3
  58. mcli/test/__init__.py +0 -1
  59. mcli/test/cron_test_cmd.py +0 -697
  60. mcli/workflow/__init__.py +0 -0
  61. mcli/workflow/daemon/__init__.py +0 -15
  62. mcli/workflow/dashboard/__init__.py +0 -5
  63. mcli/workflow/docker/__init__.py +0 -0
  64. mcli/workflow/file/__init__.py +0 -0
  65. mcli/workflow/gcloud/__init__.py +0 -1
  66. mcli/workflow/git_commit/__init__.py +0 -0
  67. mcli/workflow/interview/__init__.py +0 -0
  68. mcli/workflow/politician_trading/__init__.py +0 -4
  69. mcli/workflow/registry/__init__.py +0 -0
  70. mcli/workflow/repo/__init__.py +0 -0
  71. mcli/workflow/scheduler/__init__.py +0 -25
  72. mcli/workflow/search/__init__.py +0 -0
  73. mcli/workflow/sync/__init__.py +0 -5
  74. mcli/workflow/videos/__init__.py +0 -1
  75. mcli/workflow/wakatime/__init__.py +0 -80
  76. {mcli_framework-7.8.2.dist-info → mcli_framework-7.8.4.dist-info}/WHEEL +0 -0
  77. {mcli_framework-7.8.2.dist-info → mcli_framework-7.8.4.dist-info}/entry_points.txt +0 -0
  78. {mcli_framework-7.8.2.dist-info → mcli_framework-7.8.4.dist-info}/licenses/LICENSE +0 -0
  79. {mcli_framework-7.8.2.dist-info → mcli_framework-7.8.4.dist-info}/top_level.txt +0 -0
mcli/self/store_cmd.py ADDED
@@ -0,0 +1,424 @@
1
+ """
2
+ Command Store Management - Sync ~/.mcli/commands/ to git
3
+ Similar to lsh secrets but for workflow commands
4
+ """
5
+
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+
12
+ import click
13
+
14
+ from mcli.lib.logger.logger import get_logger
15
+ from mcli.lib.ui.styling import error, info, success, warning
16
+
17
+ logger = get_logger()
18
+
19
+ # Default store location
20
+ DEFAULT_STORE_PATH = Path.home() / "repos" / "mcli-commands"
21
+ COMMANDS_PATH = Path.home() / ".mcli" / "commands"
22
+
23
+
24
+ @click.group(name="store")
25
+ def store():
26
+ """Manage command store - sync ~/.mcli/commands/ to git"""
27
+ pass
28
+
29
+
30
+ @store.command(name="init")
31
+ @click.option("--path", "-p", type=click.Path(), help=f"Store path (default: {DEFAULT_STORE_PATH})")
32
+ @click.option("--remote", "-r", help="Git remote URL (optional)")
33
+ def init_store(path, remote):
34
+ """Initialize command store with git"""
35
+ store_path = Path(path) if path else DEFAULT_STORE_PATH
36
+
37
+ try:
38
+ # Create store directory
39
+ store_path.mkdir(parents=True, exist_ok=True)
40
+
41
+ # Initialize git if not already initialized
42
+ git_dir = store_path / ".git"
43
+ if not git_dir.exists():
44
+ subprocess.run(["git", "init"], cwd=store_path, check=True, capture_output=True)
45
+ success(f"Initialized git repository at {store_path}")
46
+
47
+ # Create .gitignore
48
+ gitignore = store_path / ".gitignore"
49
+ gitignore.write_text("*.backup\n.DS_Store\n")
50
+
51
+ # Create README
52
+ readme = store_path / "README.md"
53
+ readme.write_text(
54
+ f"""# MCLI Commands Store
55
+
56
+ Personal workflow commands for mcli framework.
57
+
58
+ ## Usage
59
+
60
+ Push commands:
61
+ ```bash
62
+ mcli self store push
63
+ ```
64
+
65
+ Pull commands:
66
+ ```bash
67
+ mcli self store pull
68
+ ```
69
+
70
+ Sync (bidirectional):
71
+ ```bash
72
+ mcli self store sync
73
+ ```
74
+
75
+ ## Structure
76
+
77
+ All JSON command files from `~/.mcli/commands/` are stored here and version controlled.
78
+
79
+ Last updated: {datetime.now().isoformat()}
80
+ """
81
+ )
82
+
83
+ # Add remote if provided
84
+ if remote:
85
+ subprocess.run(
86
+ ["git", "remote", "add", "origin", remote], cwd=store_path, check=True
87
+ )
88
+ success(f"Added remote: {remote}")
89
+ else:
90
+ info(f"Git repository already exists at {store_path}")
91
+
92
+ # Save store path to config
93
+ config_file = Path.home() / ".mcli" / "store.conf"
94
+ config_file.parent.mkdir(parents=True, exist_ok=True)
95
+ config_file.write_text(str(store_path))
96
+
97
+ success(f"Command store initialized at {store_path}")
98
+ info(f"Store path saved to {config_file}")
99
+
100
+ except subprocess.CalledProcessError as e:
101
+ error(f"Git command failed: {e}")
102
+ logger.error(f"Git init failed: {e}")
103
+ except Exception as e:
104
+ error(f"Failed to initialize store: {e}")
105
+ logger.exception(e)
106
+
107
+
108
+ @store.command(name="push")
109
+ @click.option("--message", "-m", help="Commit message")
110
+ @click.option("--all", "-a", is_flag=True, help="Push all files (including backups)")
111
+ def push_commands(message, all):
112
+ """Push commands from ~/.mcli/commands/ to git store"""
113
+ try:
114
+ store_path = _get_store_path()
115
+
116
+ # Copy commands to store
117
+ info(f"Copying commands from {COMMANDS_PATH} to {store_path}...")
118
+
119
+ copied_count = 0
120
+ for item in COMMANDS_PATH.glob("*"):
121
+ # Skip backups unless --all specified
122
+ if not all and item.name.endswith(".backup"):
123
+ continue
124
+
125
+ dest = store_path / item.name
126
+ if item.is_file():
127
+ shutil.copy2(item, dest)
128
+ copied_count += 1
129
+ elif item.is_dir():
130
+ shutil.copytree(item, dest, dirs_exist_ok=True)
131
+ copied_count += 1
132
+
133
+ success(f"Copied {copied_count} items to store")
134
+
135
+ # Git add, commit, push
136
+ subprocess.run(["git", "add", "."], cwd=store_path, check=True)
137
+
138
+ # Check if there are changes
139
+ result = subprocess.run(
140
+ ["git", "status", "--porcelain"], cwd=store_path, capture_output=True, text=True
141
+ )
142
+
143
+ if not result.stdout.strip():
144
+ info("No changes to commit")
145
+ return
146
+
147
+ # Commit with message
148
+ commit_msg = message or f"Update commands {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
149
+ subprocess.run(["git", "commit", "-m", commit_msg], cwd=store_path, check=True)
150
+ success(f"Committed changes: {commit_msg}")
151
+
152
+ # Push to remote if configured
153
+ try:
154
+ subprocess.run(["git", "push"], cwd=store_path, check=True, capture_output=True)
155
+ success("Pushed to remote")
156
+ except subprocess.CalledProcessError:
157
+ warning("No remote configured or push failed. Commands committed locally.")
158
+
159
+ except Exception as e:
160
+ error(f"Failed to push commands: {e}")
161
+ logger.exception(e)
162
+
163
+
164
+ @store.command(name="pull")
165
+ @click.option("--force", "-f", is_flag=True, help="Overwrite local commands without backup")
166
+ def pull_commands(force):
167
+ """Pull commands from git store to ~/.mcli/commands/"""
168
+ try:
169
+ store_path = _get_store_path()
170
+
171
+ # Pull from remote
172
+ try:
173
+ subprocess.run(["git", "pull"], cwd=store_path, check=True)
174
+ success("Pulled latest changes from remote")
175
+ except subprocess.CalledProcessError:
176
+ warning("No remote configured or pull failed. Using local store.")
177
+
178
+ # Backup existing commands if not force
179
+ if not force and COMMANDS_PATH.exists():
180
+ backup_dir = (
181
+ COMMANDS_PATH.parent / f"commands_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
182
+ )
183
+ shutil.copytree(COMMANDS_PATH, backup_dir)
184
+ info(f"Backed up existing commands to {backup_dir}")
185
+
186
+ # Copy from store to commands directory
187
+ info(f"Copying commands from {store_path} to {COMMANDS_PATH}...")
188
+
189
+ COMMANDS_PATH.mkdir(parents=True, exist_ok=True)
190
+
191
+ copied_count = 0
192
+ for item in store_path.glob("*"):
193
+ # Skip git directory and README
194
+ if item.name in [".git", "README.md", ".gitignore"]:
195
+ continue
196
+
197
+ dest = COMMANDS_PATH / item.name
198
+ if item.is_file():
199
+ shutil.copy2(item, dest)
200
+ copied_count += 1
201
+ elif item.is_dir():
202
+ shutil.copytree(item, dest, dirs_exist_ok=True)
203
+ copied_count += 1
204
+
205
+ success(f"Pulled {copied_count} items from store")
206
+
207
+ except Exception as e:
208
+ error(f"Failed to pull commands: {e}")
209
+ logger.exception(e)
210
+
211
+
212
+ @store.command(name="sync")
213
+ @click.option("--message", "-m", help="Commit message if pushing")
214
+ def sync_commands(message):
215
+ """Sync commands bidirectionally (pull then push if changes)"""
216
+ try:
217
+ store_path = _get_store_path()
218
+
219
+ # First pull
220
+ info("Pulling latest changes...")
221
+ try:
222
+ subprocess.run(["git", "pull"], cwd=store_path, check=True, capture_output=True)
223
+ success("Pulled from remote")
224
+ except subprocess.CalledProcessError:
225
+ warning("No remote or pull failed")
226
+
227
+ # Then push local changes
228
+ info("Pushing local changes...")
229
+
230
+ # Copy commands
231
+ for item in COMMANDS_PATH.glob("*"):
232
+ if item.name.endswith(".backup"):
233
+ continue
234
+ dest = store_path / item.name
235
+ if item.is_file():
236
+ shutil.copy2(item, dest)
237
+ elif item.is_dir():
238
+ shutil.copytree(item, dest, dirs_exist_ok=True)
239
+
240
+ # Check for changes
241
+ result = subprocess.run(
242
+ ["git", "status", "--porcelain"], cwd=store_path, capture_output=True, text=True
243
+ )
244
+
245
+ if not result.stdout.strip():
246
+ success("Everything in sync!")
247
+ return
248
+
249
+ # Commit and push
250
+ subprocess.run(["git", "add", "."], cwd=store_path, check=True)
251
+ commit_msg = message or f"Sync commands {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
252
+ subprocess.run(["git", "commit", "-m", commit_msg], cwd=store_path, check=True)
253
+
254
+ try:
255
+ subprocess.run(["git", "push"], cwd=store_path, check=True, capture_output=True)
256
+ success("Synced and pushed to remote")
257
+ except subprocess.CalledProcessError:
258
+ success("Synced locally (no remote configured)")
259
+
260
+ except Exception as e:
261
+ error(f"Sync failed: {e}")
262
+ logger.exception(e)
263
+
264
+
265
+ @store.command(name="list")
266
+ @click.option("--store", "-s", is_flag=True, help="List store instead of local")
267
+ def list_commands(store):
268
+ """List all commands"""
269
+ try:
270
+ if store:
271
+ store_path = _get_store_path()
272
+ path = store_path
273
+ title = f"Commands in store ({store_path})"
274
+ else:
275
+ path = COMMANDS_PATH
276
+ title = f"Local commands ({COMMANDS_PATH})"
277
+
278
+ click.echo(f"\n{title}:\n")
279
+
280
+ if not path.exists():
281
+ warning(f"Directory does not exist: {path}")
282
+ return
283
+
284
+ items = sorted(path.glob("*"))
285
+ if not items:
286
+ info("No commands found")
287
+ return
288
+
289
+ for item in items:
290
+ if item.name in [".git", ".gitignore", "README.md"]:
291
+ continue
292
+
293
+ if item.is_file():
294
+ size = item.stat().st_size / 1024
295
+ modified = datetime.fromtimestamp(item.stat().st_mtime).strftime("%Y-%m-%d %H:%M")
296
+ click.echo(f" 📄 {item.name:<40} {size:>8.1f} KB {modified}")
297
+ elif item.is_dir():
298
+ count = len(list(item.glob("*")))
299
+ click.echo(f" 📁 {item.name:<40} {count:>3} files")
300
+
301
+ click.echo()
302
+
303
+ except Exception as e:
304
+ error(f"Failed to list commands: {e}")
305
+ logger.exception(e)
306
+
307
+
308
+ @store.command(name="status")
309
+ def store_status():
310
+ """Show git status of command store"""
311
+ try:
312
+ store_path = _get_store_path()
313
+
314
+ click.echo(f"\n📦 Store: {store_path}\n")
315
+
316
+ # Git status
317
+ result = subprocess.run(
318
+ ["git", "status", "--short", "--branch"], cwd=store_path, capture_output=True, text=True
319
+ )
320
+
321
+ if result.stdout:
322
+ click.echo(result.stdout)
323
+
324
+ # Show remote
325
+ result = subprocess.run(
326
+ ["git", "remote", "-v"], cwd=store_path, capture_output=True, text=True
327
+ )
328
+
329
+ if result.stdout:
330
+ click.echo("\n🌐 Remotes:")
331
+ click.echo(result.stdout)
332
+ else:
333
+ info("\nNo remote configured")
334
+
335
+ click.echo()
336
+
337
+ except Exception as e:
338
+ error(f"Failed to get status: {e}")
339
+ logger.exception(e)
340
+
341
+
342
+ @store.command(name="show")
343
+ @click.argument("command_name")
344
+ @click.option("--store", "-s", is_flag=True, help="Show from store instead of local")
345
+ def show_command(command_name, store):
346
+ """Show command file contents"""
347
+ try:
348
+ if store:
349
+ store_path = _get_store_path()
350
+ path = store_path / command_name
351
+ else:
352
+ path = COMMANDS_PATH / command_name
353
+
354
+ if not path.exists():
355
+ error(f"Command not found: {command_name}")
356
+ return
357
+
358
+ if path.is_file():
359
+ click.echo(f"\n📄 {path}:\n")
360
+ click.echo(path.read_text())
361
+ else:
362
+ info(f"{command_name} is a directory")
363
+ for item in sorted(path.glob("*")):
364
+ click.echo(f" {item.name}")
365
+
366
+ click.echo()
367
+
368
+ except Exception as e:
369
+ error(f"Failed to show command: {e}")
370
+ logger.exception(e)
371
+
372
+
373
+ @store.command(name="config")
374
+ @click.option("--remote", "-r", help="Set git remote URL")
375
+ @click.option("--path", "-p", type=click.Path(), help="Change store path")
376
+ def configure_store(remote, path):
377
+ """Configure store settings"""
378
+ try:
379
+ store_path = _get_store_path()
380
+
381
+ if path:
382
+ new_path = Path(path).expanduser().resolve()
383
+ config_file = Path.home() / ".mcli" / "store.conf"
384
+ config_file.write_text(str(new_path))
385
+ success(f"Store path updated to: {new_path}")
386
+ return
387
+
388
+ if remote:
389
+ # Check if remote exists
390
+ result = subprocess.run(
391
+ ["git", "remote"], cwd=store_path, capture_output=True, text=True
392
+ )
393
+
394
+ if "origin" in result.stdout:
395
+ subprocess.run(
396
+ ["git", "remote", "set-url", "origin", remote], cwd=store_path, check=True
397
+ )
398
+ success(f"Updated remote URL: {remote}")
399
+ else:
400
+ subprocess.run(
401
+ ["git", "remote", "add", "origin", remote], cwd=store_path, check=True
402
+ )
403
+ success(f"Added remote URL: {remote}")
404
+
405
+ except Exception as e:
406
+ error(f"Configuration failed: {e}")
407
+ logger.exception(e)
408
+
409
+
410
+ def _get_store_path() -> Path:
411
+ """Get store path from config or default"""
412
+ config_file = Path.home() / ".mcli" / "store.conf"
413
+
414
+ if config_file.exists():
415
+ store_path = Path(config_file.read_text().strip())
416
+ if store_path.exists():
417
+ return store_path
418
+
419
+ # Use default
420
+ return DEFAULT_STORE_PATH
421
+
422
+
423
+ if __name__ == "__main__":
424
+ store()
mcli/test/test_cmd.py CHANGED
@@ -16,15 +16,5 @@ def test_group():
16
16
  pass
17
17
 
18
18
 
19
- # Import and register subcommands
20
- try:
21
- from mcli.test.cron_test_cmd import cron_test
22
-
23
- test_group.add_command(cron_test, name="cron")
24
- logger.debug("Added cron test command to test group")
25
- except ImportError as e:
26
- logger.debug(f"Could not load cron test command: {e}")
27
-
28
-
29
19
  if __name__ == "__main__":
30
20
  test_group()
@@ -0,0 +1,180 @@
1
+ """ML Dashboard commands for mcli."""
2
+
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import click
8
+
9
+ from mcli.lib.logger.logger import get_logger
10
+
11
+ logger = get_logger(__name__)
12
+
13
+
14
+ @click.group(name="dashboard")
15
+ def dashboard():
16
+ """ML monitoring dashboard commands."""
17
+ pass
18
+
19
+
20
+ @dashboard.command()
21
+ @click.option("--port", "-p", default=8501, help="Port to run dashboard on")
22
+ @click.option("--host", "-h", default="localhost", help="Host to bind to")
23
+ @click.option("--debug", is_flag=True, help="Run in debug mode")
24
+ @click.option(
25
+ "--variant",
26
+ "-v",
27
+ type=click.Choice(["integrated", "supabase", "training"]),
28
+ default="supabase",
29
+ help="Dashboard variant to launch (default: supabase)",
30
+ )
31
+ def launch(port, host, debug, variant):
32
+ """Launch the ML monitoring dashboard.
33
+
34
+ Variants:
35
+ - supabase: Politician trading dashboard with Supabase integration (default)
36
+ - integrated: Full ML dashboard with local database
37
+ - training: ML training dashboard
38
+ """
39
+
40
+ click.echo(f"🚀 Starting {variant.title()} Dashboard on http://{host}:{port}")
41
+
42
+ # Get the dashboard app path based on variant
43
+ dashboard_dir = Path(__file__).parent.parent.parent / "ml" / "dashboard"
44
+
45
+ if variant == "supabase":
46
+ dashboard_path = dashboard_dir / "app_supabase.py"
47
+ elif variant == "integrated":
48
+ dashboard_path = dashboard_dir / "app_integrated.py"
49
+ elif variant == "training":
50
+ dashboard_path = dashboard_dir / "app_training.py"
51
+ else:
52
+ dashboard_path = dashboard_dir / "app.py"
53
+
54
+ if not dashboard_path.exists():
55
+ click.echo(f"❌ Dashboard app not found at {dashboard_path}!")
56
+ logger.error(f"Dashboard app not found at {dashboard_path}")
57
+ sys.exit(1)
58
+
59
+ # Build streamlit command
60
+ cmd = [
61
+ sys.executable,
62
+ "-m",
63
+ "streamlit",
64
+ "run",
65
+ str(dashboard_path),
66
+ "--server.port",
67
+ str(port),
68
+ "--server.address",
69
+ host,
70
+ "--browser.gatherUsageStats",
71
+ "false",
72
+ ]
73
+
74
+ if debug:
75
+ cmd.extend(["--logger.level", "debug"])
76
+
77
+ click.echo(f"📊 Dashboard is starting from {dashboard_path.name}...")
78
+ click.echo("Press Ctrl+C to stop")
79
+
80
+ try:
81
+ subprocess.run(cmd, check=True)
82
+ except KeyboardInterrupt:
83
+ click.echo("\n⏹️ Dashboard stopped")
84
+ except subprocess.CalledProcessError as e:
85
+ click.echo(f"❌ Failed to start dashboard: {e}")
86
+ logger.error(f"Dashboard failed to start: {e}")
87
+ sys.exit(1)
88
+
89
+
90
+ @dashboard.command()
91
+ def info():
92
+ """Show dashboard information and status."""
93
+
94
+ click.echo("📊 ML Dashboard Information")
95
+ click.echo("━" * 40)
96
+
97
+ # Check if dependencies are installed
98
+ try:
99
+ import plotly
100
+ import streamlit
101
+
102
+ click.echo("✅ Dashboard dependencies installed")
103
+ click.echo(f" Streamlit version: {streamlit.__version__}")
104
+ click.echo(f" Plotly version: {plotly.__version__}")
105
+ except ImportError as e:
106
+ click.echo(f"❌ Missing dependencies: {e}")
107
+ click.echo(" Run: uv pip install streamlit plotly")
108
+
109
+ # Check Supabase configuration
110
+ import os
111
+
112
+ supabase_url = os.getenv("SUPABASE_URL")
113
+ supabase_key = os.getenv("SUPABASE_KEY") or os.getenv("SUPABASE_ANON_KEY")
114
+
115
+ click.echo("\n🔌 Supabase Configuration:")
116
+ if supabase_url:
117
+ click.echo(f" URL: {supabase_url}")
118
+ else:
119
+ click.echo(" ❌ SUPABASE_URL not set")
120
+
121
+ if supabase_key:
122
+ click.echo(f" Key: {'*' * 20}...{supabase_key[-8:]}")
123
+ else:
124
+ click.echo(" ❌ SUPABASE_KEY not set")
125
+
126
+ if not supabase_url or not supabase_key:
127
+ click.echo("\n⚠️ To configure Supabase, set environment variables:")
128
+ click.echo(" export SUPABASE_URL=https://your-project.supabase.co")
129
+ click.echo(" export SUPABASE_KEY=your-anon-key")
130
+
131
+ click.echo("\n💡 Quick start:")
132
+ click.echo(" mcli workflow dashboard launch # Launch Supabase dashboard")
133
+ click.echo(" mcli workflow dashboard launch --variant training # Launch training dashboard")
134
+ click.echo(" mcli workflow dashboard launch --port 8502 # Custom port")
135
+ click.echo(" mcli workflow dashboard launch --host 0.0.0.0 # Expose to network")
136
+
137
+
138
+ @dashboard.command()
139
+ def test():
140
+ """Test Supabase connection."""
141
+ import os
142
+
143
+ from mcli.ml.dashboard.common import get_supabase_client
144
+
145
+ click.echo("🔍 Testing Supabase connection...")
146
+ click.echo("━" * 40)
147
+
148
+ # Check environment variables
149
+ url = os.getenv("SUPABASE_URL")
150
+ key = os.getenv("SUPABASE_KEY") or os.getenv("SUPABASE_ANON_KEY")
151
+
152
+ if not url:
153
+ click.echo("❌ SUPABASE_URL not set")
154
+ sys.exit(1)
155
+
156
+ if not key:
157
+ click.echo("❌ SUPABASE_KEY or SUPABASE_ANON_KEY not set")
158
+ sys.exit(1)
159
+
160
+ click.echo(f"✅ URL configured: {url}")
161
+ click.echo(f"✅ Key configured: {'*' * 20}...{key[-8:]}")
162
+
163
+ # Try to connect
164
+ client = get_supabase_client()
165
+
166
+ if not client:
167
+ click.echo("❌ Failed to create Supabase client")
168
+ sys.exit(1)
169
+
170
+ click.echo("✅ Supabase client created")
171
+
172
+ # Try to query a table
173
+ try:
174
+ response = client.table("politicians").select("id").limit(1).execute()
175
+ click.echo(f"✅ Successfully queried 'politicians' table ({len(response.data)} records)")
176
+ except Exception as e:
177
+ click.echo(f"⚠️ Could not query 'politicians' table: {e}")
178
+ click.echo(" (Table might not exist yet)")
179
+
180
+ click.echo("\n✅ Connection test passed!")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcli-framework
3
- Version: 7.8.2
3
+ Version: 7.8.4
4
4
  Summary: Portable workflow framework - transform any script into a versioned, schedulable command. Store in ~/.mcli/commands/, 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>