mirrorneuron-cli 1.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.
- mirrorneuron_cli-1.0.0.dist-info/METADATA +73 -0
- mirrorneuron_cli-1.0.0.dist-info/RECORD +19 -0
- mirrorneuron_cli-1.0.0.dist-info/WHEEL +5 -0
- mirrorneuron_cli-1.0.0.dist-info/entry_points.txt +2 -0
- mirrorneuron_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
- mirrorneuron_cli-1.0.0.dist-info/top_level.txt +1 -0
- mn_cli/__init__.py +0 -0
- mn_cli/config.py +43 -0
- mn_cli/error_handler.py +51 -0
- mn_cli/libs/__init__.py +1 -0
- mn_cli/libs/blueprint_cmds.py +598 -0
- mn_cli/libs/job_cmds.py +160 -0
- mn_cli/libs/run_cmds.py +780 -0
- mn_cli/libs/sys_cmds.py +52 -0
- mn_cli/libs/ui.py +162 -0
- mn_cli/logging_config.py +38 -0
- mn_cli/main.py +35 -0
- mn_cli/server_cmds.py +331 -0
- mn_cli/shared.py +13 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mirrorneuron-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MirrorNeuron CLI
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: mirrorneuron-python-sdk
|
|
11
|
+
Requires-Dist: typer>=0.9.0
|
|
12
|
+
Requires-Dist: rich>=13.0.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# MirrorNeuron CLI
|
|
16
|
+
|
|
17
|
+
The official Command Line Interface for managing the MirrorNeuron distributed runtime system.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
*Note: This tool is installed automatically and symlinked globally as `mn` by the MirrorNeuron `install.sh` script.*
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install mirrorneuron-cli
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
Powered by Typer and Rich, this CLI provides elegant formatting for job lifecycle management:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
mn nodes # View system summary and available executor pools
|
|
31
|
+
mn submit ./flow.json # Submit a new workflow manifest
|
|
32
|
+
mn list # View all active and completed jobs
|
|
33
|
+
mn status <job_id> # Detailed execution state of a specific job
|
|
34
|
+
mn monitor <job_id> # Stream live execution events
|
|
35
|
+
mn cancel <job_id> # Gracefully terminate a workflow
|
|
36
|
+
mn metrics # Inspect runtime metrics summary
|
|
37
|
+
mn dead-letters <job_id> # Inspect dead-letter events
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Blueprint Commands
|
|
41
|
+
|
|
42
|
+
Blueprint execution and observability are grouped under `mn blueprint` so users have one CLI entry point:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
mn blueprint list
|
|
46
|
+
mn blueprint install
|
|
47
|
+
mn blueprint update
|
|
48
|
+
mn blueprint run <blueprint_id>
|
|
49
|
+
mn blueprint run ./path/to/bundle_or_source_blueprint
|
|
50
|
+
mn blueprint run <blueprint_id> --offline
|
|
51
|
+
mn blueprint run <blueprint_id> --revision <git_sha_or_tag>
|
|
52
|
+
mn blueprint monitor --follow
|
|
53
|
+
mn blueprint tail <run_id>
|
|
54
|
+
mn blueprint compare <run_a> <run_b>
|
|
55
|
+
mn blueprint export <run_id> --format markdown
|
|
56
|
+
mn blueprint export <run_id> --format html
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`mn blueprint run` accepts either an installed blueprint ID or a local folder. If the local folder is already a bundle, the CLI submits it directly; if it is a Python source blueprint, the CLI generates a bundle under `~/.mn/generated_blueprint_bundles/<run_id>/` first. Catalog runs use the cached blueprint library by default so runs are not silently changed by a network pull. Use `mn blueprint update` or `mn blueprint run <id> --update` when you want to refresh the cache. Each blueprint submission pre-generates a shared `MN_RUN_ID`, injects `MN_BLUEPRINT_CONFIG_JSON` into runtime workers, and prints both the blueprint run ID and runtime job ID.
|
|
60
|
+
|
|
61
|
+
Blueprint run artifacts are read from the shared run store at `~/.mn/runs/<run_id>/`.
|
|
62
|
+
Use `--runs-root <path>` with `monitor`, `tail`, `compare`, or `export` when inspecting a custom run directory.
|
|
63
|
+
When a blueprint registers a shared or custom web UI, `monitor` shows the local URL and `export --format html` creates a static report page.
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
All overrides use `MN_` env vars:
|
|
68
|
+
|
|
69
|
+
- `MN_GRPC_TARGET`: core gRPC target.
|
|
70
|
+
- `MN_GRPC_TIMEOUT_SECONDS`: RPC timeout; `0` or `none` disables it.
|
|
71
|
+
- `MN_GRPC_AUTH_TOKEN`: optional bearer metadata for protected core gateways.
|
|
72
|
+
- `MN_CLI_LOG_PATH`: error log path.
|
|
73
|
+
- `MN_CLI_OUTPUT=plain`: disable Rich color output.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
mirrorneuron_cli-1.0.0.dist-info/licenses/LICENSE,sha256=wdEmA-XJ6EaXHsjFyuRKhT-owNT5-ucLf6jbtOz0yOM,1072
|
|
2
|
+
mn_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
mn_cli/config.py,sha256=fJfjiM__uHGStgz7jsuSN52HK3LOv76omhwWmR74hEk,1343
|
|
4
|
+
mn_cli/error_handler.py,sha256=2k1PLN777U8EY90BpM3N7XtO3osPbzPzmraclWYYgYI,2276
|
|
5
|
+
mn_cli/logging_config.py,sha256=AXHLgpcJHPEGd-axGrtTt_bslRQwr6eQ8Dxvmp0jK_I,982
|
|
6
|
+
mn_cli/main.py,sha256=jShrxF1wszLKhKND4y6O5y3iQ007CO05pzwYZGBxpTc,1100
|
|
7
|
+
mn_cli/server_cmds.py,sha256=n4rctjJbx9ZvCNMpIHniXvsO2QhSRhZ_HZvM7phPDJE,12162
|
|
8
|
+
mn_cli/shared.py,sha256=aK2_mDXezAZsCiyRskPxdXmxUcjubKB7yGUGNzu7ylo,420
|
|
9
|
+
mn_cli/libs/__init__.py,sha256=1IXSLNPLt2HhYADcj4mt_jQBK0nd1tCwkIfIwB6ycYo,26
|
|
10
|
+
mn_cli/libs/blueprint_cmds.py,sha256=l7OBr4rS6c5febGLbUscoqP9xyDTqvrEPI8kD3l2fSc,24481
|
|
11
|
+
mn_cli/libs/job_cmds.py,sha256=cO4AoqTVRQNLzQGRCGZUBnUa_3ATT6g_8Lp9BKb-9qc,5447
|
|
12
|
+
mn_cli/libs/run_cmds.py,sha256=AYaH7J1RYhDAEVDDld2LLg2OLjRjng7OtWyMSkd7hO0,30046
|
|
13
|
+
mn_cli/libs/sys_cmds.py,sha256=LIeWoxUwZADMZfTnkrz1L5MIP3wugE_yiKjm9LRvKBU,1881
|
|
14
|
+
mn_cli/libs/ui.py,sha256=3qyEx2d0JFidcvP1IRq246D8l5rBaXqrlkOFx9NHHh0,5824
|
|
15
|
+
mirrorneuron_cli-1.0.0.dist-info/METADATA,sha256=njQm3tMNA6dtz5eN95G4_WZm7xgwq6zh3sfPLq4bv-Q,3162
|
|
16
|
+
mirrorneuron_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
17
|
+
mirrorneuron_cli-1.0.0.dist-info/entry_points.txt,sha256=e7vGuYEi_HV-mIYTbRbhfTGQMn04y-ItDDAdCTMYHdQ,39
|
|
18
|
+
mirrorneuron_cli-1.0.0.dist-info/top_level.txt,sha256=yE-PY4RVgDSFkh0okxYE9UHWVBU5xEsyL8G2ZA_xEAE,7
|
|
19
|
+
mirrorneuron_cli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MirrorNeuronLab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mn_cli
|
mn_cli/__init__.py
ADDED
|
File without changes
|
mn_cli/config.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class CliConfig:
|
|
10
|
+
grpc_target: str = "localhost:50051"
|
|
11
|
+
grpc_timeout_seconds: float | None = 10.0
|
|
12
|
+
grpc_auth_token: str = ""
|
|
13
|
+
log_path: Path = Path.home() / ".mn" / "logs" / "cli.log"
|
|
14
|
+
output_mode: str = "rich"
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_env(cls) -> "CliConfig":
|
|
18
|
+
core_host = os.getenv("MN_CORE_HOST", "localhost")
|
|
19
|
+
return cls(
|
|
20
|
+
grpc_target=os.getenv(
|
|
21
|
+
"MN_GRPC_TARGET",
|
|
22
|
+
os.getenv("MN_CORE_GRPC_TARGET", f"{core_host}:50051"),
|
|
23
|
+
),
|
|
24
|
+
grpc_timeout_seconds=_timeout(),
|
|
25
|
+
grpc_auth_token=os.getenv("MN_GRPC_AUTH_TOKEN", ""),
|
|
26
|
+
log_path=Path(
|
|
27
|
+
os.getenv(
|
|
28
|
+
"MN_CLI_LOG_PATH",
|
|
29
|
+
str(Path.home() / ".mn" / "logs" / "cli.log"),
|
|
30
|
+
)
|
|
31
|
+
).expanduser(),
|
|
32
|
+
output_mode=os.getenv("MN_CLI_OUTPUT", "rich"),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _timeout() -> float | None:
|
|
37
|
+
value = os.getenv("MN_GRPC_TIMEOUT_SECONDS", "10")
|
|
38
|
+
if value.lower() in {"", "none", "0"}:
|
|
39
|
+
return None
|
|
40
|
+
try:
|
|
41
|
+
return float(value)
|
|
42
|
+
except ValueError as exc:
|
|
43
|
+
raise ValueError("MN_GRPC_TIMEOUT_SECONDS must be a number, 0, or none") from exc
|
mn_cli/error_handler.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import grpc
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from mn_cli.config import CliConfig
|
|
5
|
+
from mn_cli.logging_config import configure_logging
|
|
6
|
+
|
|
7
|
+
log_file = CliConfig.from_env().log_path
|
|
8
|
+
logger = configure_logging("mn-cli", log_file)
|
|
9
|
+
|
|
10
|
+
CONTEXT_MESSAGES = {
|
|
11
|
+
"submit": "Error submitting job",
|
|
12
|
+
"status": "Error fetching job status",
|
|
13
|
+
"list_jobs": "Error listing jobs",
|
|
14
|
+
"clear": "Error clearing jobs",
|
|
15
|
+
"cancel": "Error cancelling job",
|
|
16
|
+
"pause": "Error pausing job",
|
|
17
|
+
"resume": "Error resuming job",
|
|
18
|
+
"nodes": "Error fetching nodes",
|
|
19
|
+
"metrics": "Error fetching metrics",
|
|
20
|
+
"dead_letters": "Error listing dead letters",
|
|
21
|
+
"run bundle": "Error running bundle",
|
|
22
|
+
"monitor stream": "Error fetching job",
|
|
23
|
+
"fetch results": "Error fetching results",
|
|
24
|
+
"validate": "Validation failed",
|
|
25
|
+
"leave": "Error removing node",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def handle_cli_error(e: Exception, console: Console, context: str = ""):
|
|
29
|
+
"""Handle exceptions gracefully, log the full trace, and print a friendly message."""
|
|
30
|
+
logger.exception(f"Error during {context}")
|
|
31
|
+
|
|
32
|
+
if isinstance(e, grpc.RpcError):
|
|
33
|
+
code = e.code()
|
|
34
|
+
details = e.details()
|
|
35
|
+
|
|
36
|
+
if code == grpc.StatusCode.NOT_FOUND:
|
|
37
|
+
console.print(f"[red]Error: Cannot find the job by ID. ({details})[/red]")
|
|
38
|
+
elif code == grpc.StatusCode.INTERNAL and "not found" in str(details).lower():
|
|
39
|
+
console.print(f"[red]Error: Cannot find the job by ID. ({details})[/red]")
|
|
40
|
+
elif code == grpc.StatusCode.INTERNAL and "terminal state" in str(details).lower():
|
|
41
|
+
console.print(f"[red]Error: Job is already in a terminal state and cannot be modified.[/red]")
|
|
42
|
+
elif code == grpc.StatusCode.RESOURCE_EXHAUSTED:
|
|
43
|
+
console.print("[yellow]Runtime is under CPU/GPU/memory pressure and is not accepting new jobs.[/yellow]")
|
|
44
|
+
console.print(f"[dim]{details}[/dim]")
|
|
45
|
+
else:
|
|
46
|
+
console.print(f"[red]Communication Error: {details} (Code: {code.name})[/red]")
|
|
47
|
+
console.print(f"[dim]See {log_file} for full details.[/dim]")
|
|
48
|
+
else:
|
|
49
|
+
prefix = CONTEXT_MESSAGES.get(context, "Error")
|
|
50
|
+
console.print(f"[red]{prefix}: {str(e)}[/red]")
|
|
51
|
+
console.print(f"[dim]See {log_file} for full details.[/dim]")
|
mn_cli/libs/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Initialization for libs
|