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
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation utilities for workflow notebooks.
|
|
3
|
+
|
|
4
|
+
Provides validation for:
|
|
5
|
+
- JSON schema compliance
|
|
6
|
+
- Code syntax checking
|
|
7
|
+
- Shell script validation
|
|
8
|
+
- MCLI API validation
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
import subprocess
|
|
13
|
+
import tempfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from mcli.lib.logger.logger import get_logger
|
|
18
|
+
|
|
19
|
+
from .schema import NOTEBOOK_SCHEMA, CellLanguage, CellType, WorkflowNotebook
|
|
20
|
+
|
|
21
|
+
logger = get_logger()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NotebookValidator:
|
|
25
|
+
"""Validator for workflow notebooks."""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.schema_errors: List[str] = []
|
|
29
|
+
self.syntax_errors: List[str] = []
|
|
30
|
+
|
|
31
|
+
def validate_schema(self, notebook: WorkflowNotebook) -> bool:
|
|
32
|
+
"""
|
|
33
|
+
Validate notebook against JSON schema.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
notebook: WorkflowNotebook to validate
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
True if valid, False otherwise
|
|
40
|
+
"""
|
|
41
|
+
self.schema_errors = []
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import jsonschema
|
|
45
|
+
|
|
46
|
+
data = notebook.to_dict()
|
|
47
|
+
jsonschema.validate(instance=data, schema=NOTEBOOK_SCHEMA)
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
except ImportError:
|
|
51
|
+
# jsonschema not installed, do basic validation
|
|
52
|
+
logger.warning("jsonschema not installed, performing basic validation")
|
|
53
|
+
return self._basic_schema_validation(notebook)
|
|
54
|
+
|
|
55
|
+
except Exception as e:
|
|
56
|
+
self.schema_errors.append(str(e))
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def _basic_schema_validation(self, notebook: WorkflowNotebook) -> bool:
|
|
60
|
+
"""Basic schema validation without jsonschema library."""
|
|
61
|
+
valid = True
|
|
62
|
+
|
|
63
|
+
# Check required fields
|
|
64
|
+
if not notebook.metadata.mcli.name:
|
|
65
|
+
self.schema_errors.append("Missing required field: metadata.mcli.name")
|
|
66
|
+
valid = False
|
|
67
|
+
|
|
68
|
+
if notebook.nbformat != 4:
|
|
69
|
+
self.schema_errors.append(f"Invalid nbformat: {notebook.nbformat} (expected 4)")
|
|
70
|
+
valid = False
|
|
71
|
+
|
|
72
|
+
# Validate cells
|
|
73
|
+
for i, cell in enumerate(notebook.cells):
|
|
74
|
+
if not cell.cell_type:
|
|
75
|
+
self.schema_errors.append(f"Cell {i}: Missing cell_type")
|
|
76
|
+
valid = False
|
|
77
|
+
|
|
78
|
+
if not cell.source and not isinstance(cell.source, (str, list)):
|
|
79
|
+
self.schema_errors.append(f"Cell {i}: Missing or invalid source")
|
|
80
|
+
valid = False
|
|
81
|
+
|
|
82
|
+
return valid
|
|
83
|
+
|
|
84
|
+
def validate_syntax(self, notebook: WorkflowNotebook) -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Validate code syntax in all code cells.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
notebook: WorkflowNotebook to validate
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if all code is syntactically valid, False otherwise
|
|
93
|
+
"""
|
|
94
|
+
self.syntax_errors = []
|
|
95
|
+
all_valid = True
|
|
96
|
+
|
|
97
|
+
for i, cell in enumerate(notebook.cells):
|
|
98
|
+
if cell.cell_type != CellType.CODE:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
language = cell.language
|
|
102
|
+
code = cell.source_text
|
|
103
|
+
|
|
104
|
+
if language == CellLanguage.PYTHON:
|
|
105
|
+
if not self._validate_python_syntax(code, i):
|
|
106
|
+
all_valid = False
|
|
107
|
+
|
|
108
|
+
elif language in (CellLanguage.SHELL, CellLanguage.BASH, CellLanguage.ZSH):
|
|
109
|
+
if not self._validate_shell_syntax(code, i):
|
|
110
|
+
all_valid = False
|
|
111
|
+
|
|
112
|
+
return all_valid
|
|
113
|
+
|
|
114
|
+
def _validate_python_syntax(self, code: str, cell_index: int) -> bool:
|
|
115
|
+
"""Validate Python code syntax."""
|
|
116
|
+
try:
|
|
117
|
+
ast.parse(code)
|
|
118
|
+
return True
|
|
119
|
+
except SyntaxError as e:
|
|
120
|
+
self.syntax_errors.append(
|
|
121
|
+
f"Cell {cell_index} (Python): Syntax error at line {e.lineno}: {e.msg}"
|
|
122
|
+
)
|
|
123
|
+
return False
|
|
124
|
+
except Exception as e:
|
|
125
|
+
self.syntax_errors.append(f"Cell {cell_index} (Python): {str(e)}")
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
def _validate_shell_syntax(self, code: str, cell_index: int) -> bool:
|
|
129
|
+
"""Validate shell script syntax using bash -n."""
|
|
130
|
+
try:
|
|
131
|
+
# Create temporary file with shell script
|
|
132
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=False) as f:
|
|
133
|
+
f.write(code)
|
|
134
|
+
temp_path = f.name
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
# Use bash -n to check syntax without executing
|
|
138
|
+
result = subprocess.run(
|
|
139
|
+
["bash", "-n", temp_path],
|
|
140
|
+
capture_output=True,
|
|
141
|
+
text=True,
|
|
142
|
+
timeout=5,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if result.returncode != 0:
|
|
146
|
+
error_msg = result.stderr.strip()
|
|
147
|
+
self.syntax_errors.append(f"Cell {cell_index} (Shell): {error_msg}")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
finally:
|
|
153
|
+
# Clean up temp file
|
|
154
|
+
Path(temp_path).unlink(missing_ok=True)
|
|
155
|
+
|
|
156
|
+
except subprocess.TimeoutExpired:
|
|
157
|
+
self.syntax_errors.append(f"Cell {cell_index} (Shell): Validation timeout")
|
|
158
|
+
return False
|
|
159
|
+
except Exception as e:
|
|
160
|
+
self.syntax_errors.append(f"Cell {cell_index} (Shell): {str(e)}")
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
def validate_mcli_apis(self, notebook: WorkflowNotebook) -> bool:
|
|
164
|
+
"""
|
|
165
|
+
Validate MCLI API usage in code cells.
|
|
166
|
+
|
|
167
|
+
This checks for:
|
|
168
|
+
- Proper Click decorator usage
|
|
169
|
+
- MCLI library imports
|
|
170
|
+
- Common API patterns
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
notebook: WorkflowNotebook to validate
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if API usage is valid, False otherwise
|
|
177
|
+
"""
|
|
178
|
+
# TODO: Implement MCLI-specific API validation
|
|
179
|
+
# This could check for:
|
|
180
|
+
# - @click.command() or @click.group() decorators
|
|
181
|
+
# - Proper import statements
|
|
182
|
+
# - Common anti-patterns
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
def get_all_errors(self) -> List[str]:
|
|
186
|
+
"""Get all validation errors."""
|
|
187
|
+
return self.schema_errors + self.syntax_errors
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class CodeLinter:
|
|
191
|
+
"""Linter for workflow notebook code."""
|
|
192
|
+
|
|
193
|
+
def __init__(self):
|
|
194
|
+
self.issues: List[Dict[str, any]] = []
|
|
195
|
+
|
|
196
|
+
def lint_python(self, code: str) -> List[Dict[str, any]]:
|
|
197
|
+
"""
|
|
198
|
+
Lint Python code using available linters.
|
|
199
|
+
|
|
200
|
+
Tries to use (in order):
|
|
201
|
+
1. flake8
|
|
202
|
+
2. pylint
|
|
203
|
+
3. Basic AST-based checks
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
List of lint issues
|
|
207
|
+
"""
|
|
208
|
+
self.issues = []
|
|
209
|
+
|
|
210
|
+
# Try flake8
|
|
211
|
+
try:
|
|
212
|
+
import flake8.api.legacy as flake8_api
|
|
213
|
+
|
|
214
|
+
style_guide = flake8_api.get_style_guide()
|
|
215
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
|
216
|
+
f.write(code)
|
|
217
|
+
temp_path = f.name
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
report = style_guide.check_files([temp_path])
|
|
221
|
+
# Convert flake8 results to our format
|
|
222
|
+
# (this is simplified - actual implementation would parse flake8 output)
|
|
223
|
+
if report.total_errors > 0:
|
|
224
|
+
self.issues.append(
|
|
225
|
+
{
|
|
226
|
+
"severity": "warning",
|
|
227
|
+
"message": f"Found {report.total_errors} style issues",
|
|
228
|
+
"line": 0,
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
finally:
|
|
232
|
+
Path(temp_path).unlink(missing_ok=True)
|
|
233
|
+
|
|
234
|
+
except ImportError:
|
|
235
|
+
# flake8 not available, try basic checks
|
|
236
|
+
self._basic_python_lint(code)
|
|
237
|
+
|
|
238
|
+
return self.issues
|
|
239
|
+
|
|
240
|
+
def _basic_python_lint(self, code: str):
|
|
241
|
+
"""Basic Python linting using AST."""
|
|
242
|
+
try:
|
|
243
|
+
tree = ast.parse(code)
|
|
244
|
+
|
|
245
|
+
# Check for common issues
|
|
246
|
+
for node in ast.walk(tree):
|
|
247
|
+
# Check for unused imports (simplified)
|
|
248
|
+
if isinstance(node, ast.Import):
|
|
249
|
+
# TODO: Check if imports are actually used
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
# Check for bare except
|
|
253
|
+
if isinstance(node, ast.ExceptHandler) and node.type is None:
|
|
254
|
+
self.issues.append(
|
|
255
|
+
{
|
|
256
|
+
"severity": "warning",
|
|
257
|
+
"message": "Bare except clause - consider specifying exception type",
|
|
258
|
+
"line": node.lineno,
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
except SyntaxError:
|
|
263
|
+
# Already caught by syntax validation
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
def lint_shell(self, code: str) -> List[Dict[str, any]]:
|
|
267
|
+
"""
|
|
268
|
+
Lint shell script using shellcheck if available.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
List of lint issues
|
|
272
|
+
"""
|
|
273
|
+
self.issues = []
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
# Create temporary file
|
|
277
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=False) as f:
|
|
278
|
+
f.write(code)
|
|
279
|
+
temp_path = f.name
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
# Try to use shellcheck
|
|
283
|
+
result = subprocess.run(
|
|
284
|
+
["shellcheck", "-f", "json", temp_path],
|
|
285
|
+
capture_output=True,
|
|
286
|
+
text=True,
|
|
287
|
+
timeout=10,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if result.stdout:
|
|
291
|
+
import json
|
|
292
|
+
|
|
293
|
+
issues = json.loads(result.stdout)
|
|
294
|
+
self.issues = [
|
|
295
|
+
{
|
|
296
|
+
"severity": issue.get("level", "warning"),
|
|
297
|
+
"message": issue.get("message", ""),
|
|
298
|
+
"line": issue.get("line", 0),
|
|
299
|
+
"code": issue.get("code", ""),
|
|
300
|
+
}
|
|
301
|
+
for issue in issues
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
finally:
|
|
305
|
+
Path(temp_path).unlink(missing_ok=True)
|
|
306
|
+
|
|
307
|
+
except FileNotFoundError:
|
|
308
|
+
# shellcheck not installed
|
|
309
|
+
logger.debug("shellcheck not found, skipping shell linting")
|
|
310
|
+
except Exception as e:
|
|
311
|
+
logger.warning(f"Shell linting failed: {e}")
|
|
312
|
+
|
|
313
|
+
return self.issues
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Secrets workflow command - migrated from lib.secrets
|
|
3
|
+
|
|
4
|
+
This is now a workflow instead of a lib utility.
|
|
5
|
+
All secrets management functionality remains the same.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from mcli.lib.ui.styling import error, info, success, warning
|
|
14
|
+
|
|
15
|
+
# Import from the original lib.secrets modules (keeping the implementation)
|
|
16
|
+
from mcli.lib.secrets.manager import SecretsManager
|
|
17
|
+
from mcli.lib.secrets.repl import run_repl
|
|
18
|
+
from mcli.lib.secrets.store import SecretsStore
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command(name="secrets", help="Secure secrets management with encryption and git sync")
|
|
22
|
+
@click.option("--repl", is_flag=True, help="Launch interactive secrets shell")
|
|
23
|
+
@click.option("--set", "set_secret", nargs=2, type=str, help="Set a secret (KEY VALUE)")
|
|
24
|
+
@click.option("--get", "get_secret", type=str, help="Get a secret value")
|
|
25
|
+
@click.option("--list", "list_secrets", is_flag=True, help="List all secrets")
|
|
26
|
+
@click.option("--delete", "delete_secret", type=str, help="Delete a secret")
|
|
27
|
+
@click.option("--namespace", "-n", default="default", help="Namespace for secrets")
|
|
28
|
+
@click.option("--show", is_flag=True, help="Show full value (not masked)")
|
|
29
|
+
@click.option("--export", is_flag=True, help="Export secrets as environment variables")
|
|
30
|
+
@click.option("--import-file", type=click.Path(exists=True), help="Import from env file")
|
|
31
|
+
@click.option("--store-init", is_flag=True, help="Initialize secrets store")
|
|
32
|
+
@click.option("--store-push", is_flag=True, help="Push secrets to store")
|
|
33
|
+
@click.option("--store-pull", is_flag=True, help="Pull secrets from store")
|
|
34
|
+
@click.option("--store-sync", is_flag=True, help="Sync secrets with store")
|
|
35
|
+
@click.option("--store-status", is_flag=True, help="Show store status")
|
|
36
|
+
@click.option("--remote", type=str, help="Git remote URL (for store init)")
|
|
37
|
+
@click.option("--message", "-m", type=str, help="Commit message (for store operations)")
|
|
38
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file (for export)")
|
|
39
|
+
def secrets(
|
|
40
|
+
repl: bool,
|
|
41
|
+
set_secret: Optional[tuple],
|
|
42
|
+
get_secret: Optional[str],
|
|
43
|
+
list_secrets: bool,
|
|
44
|
+
delete_secret: Optional[str],
|
|
45
|
+
namespace: str,
|
|
46
|
+
show: bool,
|
|
47
|
+
export: bool,
|
|
48
|
+
import_file: Optional[str],
|
|
49
|
+
store_init: bool,
|
|
50
|
+
store_push: bool,
|
|
51
|
+
store_pull: bool,
|
|
52
|
+
store_sync: bool,
|
|
53
|
+
store_status: bool,
|
|
54
|
+
remote: Optional[str],
|
|
55
|
+
message: Optional[str],
|
|
56
|
+
output: Optional[str],
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Secrets management workflow - all-in-one command for managing secrets.
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
mcli workflows secrets --repl # Interactive shell
|
|
63
|
+
mcli workflows secrets --set API_KEY abc123 # Set a secret
|
|
64
|
+
mcli workflows secrets --get API_KEY # Get a secret
|
|
65
|
+
mcli workflows secrets --list # List all secrets
|
|
66
|
+
mcli workflows secrets --export # Export as env vars
|
|
67
|
+
mcli workflows secrets --store-init # Initialize git store
|
|
68
|
+
"""
|
|
69
|
+
manager = SecretsManager()
|
|
70
|
+
|
|
71
|
+
# Handle REPL
|
|
72
|
+
if repl:
|
|
73
|
+
run_repl()
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Handle set
|
|
77
|
+
if set_secret:
|
|
78
|
+
key, value = set_secret
|
|
79
|
+
try:
|
|
80
|
+
manager.set(key, value, namespace)
|
|
81
|
+
success(f"Secret '{key}' set in namespace '{namespace}'")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
error(f"Failed to set secret: {e}")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
# Handle get
|
|
87
|
+
if get_secret:
|
|
88
|
+
value = manager.get(get_secret, namespace)
|
|
89
|
+
if value is not None:
|
|
90
|
+
if show:
|
|
91
|
+
click.echo(value)
|
|
92
|
+
else:
|
|
93
|
+
masked = (
|
|
94
|
+
value[:3] + "*" * (len(value) - 6) + value[-3:]
|
|
95
|
+
if len(value) > 6
|
|
96
|
+
else "*" * len(value)
|
|
97
|
+
)
|
|
98
|
+
info(f"{get_secret} = {masked}")
|
|
99
|
+
info("Use --show to display the full value")
|
|
100
|
+
else:
|
|
101
|
+
warning(f"Secret '{get_secret}' not found in namespace '{namespace}'")
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
# Handle list
|
|
105
|
+
if list_secrets:
|
|
106
|
+
secrets_list = manager.list(namespace if namespace != "default" else None)
|
|
107
|
+
if secrets_list:
|
|
108
|
+
info("Secrets:")
|
|
109
|
+
for secret in secrets_list:
|
|
110
|
+
click.echo(f" • {secret}")
|
|
111
|
+
else:
|
|
112
|
+
info("No secrets found")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# Handle delete
|
|
116
|
+
if delete_secret:
|
|
117
|
+
if click.confirm(f"Are you sure you want to delete '{delete_secret}'?"):
|
|
118
|
+
if manager.delete(delete_secret, namespace):
|
|
119
|
+
success(f"Secret '{delete_secret}' deleted from namespace '{namespace}'")
|
|
120
|
+
else:
|
|
121
|
+
warning(f"Secret '{delete_secret}' not found in namespace '{namespace}'")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
# Handle export
|
|
125
|
+
if export:
|
|
126
|
+
env_vars = manager.export_env(namespace if namespace != "default" else None)
|
|
127
|
+
if env_vars:
|
|
128
|
+
if output:
|
|
129
|
+
with open(output, "w") as f:
|
|
130
|
+
for key, value in env_vars.items():
|
|
131
|
+
f.write(f"export {key}={value}\n")
|
|
132
|
+
success(f"Exported {len(env_vars)} secrets to {output}")
|
|
133
|
+
else:
|
|
134
|
+
for key, value in env_vars.items():
|
|
135
|
+
click.echo(f"export {key}={value}")
|
|
136
|
+
else:
|
|
137
|
+
info("No secrets to export")
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
# Handle import
|
|
141
|
+
if import_file:
|
|
142
|
+
count = manager.import_env(Path(import_file), namespace)
|
|
143
|
+
success(f"Imported {count} secrets into namespace '{namespace}'")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Store operations
|
|
147
|
+
store = SecretsStore()
|
|
148
|
+
|
|
149
|
+
if store_init:
|
|
150
|
+
store.init(remote)
|
|
151
|
+
success("Secrets store initialized")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
if store_push:
|
|
155
|
+
store.push(manager.secrets_dir, message)
|
|
156
|
+
success("Secrets pushed to store")
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
if store_pull:
|
|
160
|
+
store.pull(manager.secrets_dir)
|
|
161
|
+
success("Secrets pulled from store")
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
if store_sync:
|
|
165
|
+
store.sync(manager.secrets_dir, message)
|
|
166
|
+
success("Secrets synced with store")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
if store_status:
|
|
170
|
+
status = store.status()
|
|
171
|
+
info("Secrets Store Status:")
|
|
172
|
+
click.echo(f" Initialized: {status['initialized']}")
|
|
173
|
+
click.echo(f" Path: {status['store_path']}")
|
|
174
|
+
|
|
175
|
+
if status["initialized"]:
|
|
176
|
+
click.echo(f" Branch: {status['branch']}")
|
|
177
|
+
click.echo(f" Commit: {status['commit']}")
|
|
178
|
+
click.echo(f" Clean: {status['clean']}")
|
|
179
|
+
|
|
180
|
+
if status["has_remote"]:
|
|
181
|
+
click.echo(f" Remote: {status['remote_url']}")
|
|
182
|
+
else:
|
|
183
|
+
click.echo(" Remote: Not configured")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# If no action specified, show help
|
|
187
|
+
ctx = click.get_current_context()
|
|
188
|
+
click.echo(ctx.get_help())
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
secrets()
|
mcli/workflow/workflow.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Workflows command group for mcli.
|
|
3
3
|
|
|
4
4
|
All workflow commands are now loaded from portable JSON files in ~/.mcli/commands/
|
|
5
5
|
This provides a clean, maintainable way to manage workflow commands.
|
|
@@ -8,11 +8,41 @@ This provides a clean, maintainable way to manage workflow commands.
|
|
|
8
8
|
import click
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@click.group(name="
|
|
12
|
-
def
|
|
13
|
-
"""
|
|
11
|
+
@click.group(name="workflows")
|
|
12
|
+
def workflows():
|
|
13
|
+
"""Runnable workflows for automation, video processing, and daemon management"""
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
# Add secrets workflow
|
|
18
|
+
try:
|
|
19
|
+
from mcli.workflow.secrets.secrets_cmd import secrets
|
|
20
|
+
|
|
21
|
+
workflows.add_command(secrets)
|
|
22
|
+
except ImportError as e:
|
|
23
|
+
# Secrets workflow not available
|
|
24
|
+
import sys
|
|
25
|
+
from mcli.lib.logger.logger import get_logger
|
|
26
|
+
|
|
27
|
+
logger = get_logger()
|
|
28
|
+
logger.debug(f"Secrets workflow not available: {e}")
|
|
29
|
+
|
|
30
|
+
# Add notebook subcommand
|
|
31
|
+
try:
|
|
32
|
+
from mcli.workflow.notebook.notebook_cmd import notebook
|
|
33
|
+
|
|
34
|
+
workflows.add_command(notebook)
|
|
35
|
+
except ImportError as e:
|
|
36
|
+
# Notebook commands not available
|
|
37
|
+
import sys
|
|
38
|
+
from mcli.lib.logger.logger import get_logger
|
|
39
|
+
|
|
40
|
+
logger = get_logger()
|
|
41
|
+
logger.debug(f"Notebook commands not available: {e}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# For backward compatibility, keep workflow as an alias
|
|
45
|
+
workflow = workflows
|
|
46
|
+
|
|
17
47
|
if __name__ == "__main__":
|
|
18
|
-
|
|
48
|
+
workflows()
|