mirrorneuron-cli 1.1.9__tar.gz → 1.2.5__tar.gz
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.1.9 → mirrorneuron_cli-1.2.5}/.gitignore +17 -1
- mirrorneuron_cli-1.2.5/.python-version +1 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/AGENTS.md +1 -0
- {mirrorneuron_cli-1.1.9/mirrorneuron_cli.egg-info → mirrorneuron_cli-1.2.5}/PKG-INFO +5 -4
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/README.md +3 -3
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5/mirrorneuron_cli.egg-info}/PKG-INFO +5 -4
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mirrorneuron_cli.egg-info/SOURCES.txt +23 -1
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mirrorneuron_cli.egg-info/requires.txt +1 -0
- mirrorneuron_cli-1.2.5/mn_cli/config.py +38 -0
- mirrorneuron_cli-1.2.5/mn_cli/libs/artifacts.py +139 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/backup_cmds.py +20 -8
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/blueprint_cmds.py +741 -54
- mirrorneuron_cli-1.2.5/mn_cli/libs/blueprint_models.py +118 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/blueprint_repository.py +25 -16
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/blueprint_resources.py +62 -55
- mirrorneuron_cli-1.2.5/mn_cli/libs/bundles.py +40 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/deployment_cmds.py +68 -29
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/event_relay.py +0 -2
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/job_cmds.py +185 -22
- mirrorneuron_cli-1.2.5/mn_cli/libs/model_cmds.py +542 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/resource_cmds.py +65 -9
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/run_cmds.py +764 -454
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/run_logs.py +5 -4
- mirrorneuron_cli-1.2.5/mn_cli/libs/run_manifest.py +1131 -0
- mirrorneuron_cli-1.2.5/mn_cli/libs/runtime_health.py +322 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/schedule_cmds.py +78 -12
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/service_cmds.py +9 -3
- mirrorneuron_cli-1.2.5/mn_cli/libs/skill_runtime.py +668 -0
- mirrorneuron_cli-1.2.5/mn_cli/libs/sys_cmds.py +354 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/ui.py +89 -3
- mirrorneuron_cli-1.2.5/mn_cli/libs/workflow_progress.py +318 -0
- mirrorneuron_cli-1.2.5/mn_cli/libs/workflow_validation.py +677 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/main.py +76 -8
- mirrorneuron_cli-1.2.5/mn_cli/runtime_mode.py +66 -0
- mirrorneuron_cli-1.2.5/mn_cli/runtime_state.py +135 -0
- mirrorneuron_cli-1.2.5/mn_cli/schemas/workflow_manifest.schema.json +261 -0
- mirrorneuron_cli-1.2.5/mn_cli/sdk_path.py +12 -0
- mirrorneuron_cli-1.2.5/mn_cli/server_cmds.py +3539 -0
- mirrorneuron_cli-1.2.5/mn_cli/shared.py +56 -0
- mirrorneuron_cli-1.2.5/mn_cli/terminal.py +25 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/update_cmds.py +124 -73
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/pyproject.toml +4 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/test_backup_cmds.py +52 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/test_blueprint_cmds.py +386 -37
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/test_blueprint_repository.py +6 -3
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/test_deployment_cmds.py +18 -2
- mirrorneuron_cli-1.2.5/tests/test_docker_network_integration.py +101 -0
- mirrorneuron_cli-1.2.5/tests/test_job_cmds.py +57 -0
- mirrorneuron_cli-1.2.5/tests/test_main.py +178 -0
- mirrorneuron_cli-1.2.5/tests/test_model_cmds.py +356 -0
- mirrorneuron_cli-1.2.5/tests/test_resource_cmds.py +52 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/test_run_cmds.py +523 -82
- mirrorneuron_cli-1.2.5/tests/test_run_helpers.py +1092 -0
- mirrorneuron_cli-1.2.5/tests/test_runtime_health.py +329 -0
- mirrorneuron_cli-1.2.5/tests/test_runtime_mode.py +57 -0
- mirrorneuron_cli-1.2.5/tests/test_runtime_state.py +84 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/test_schedule_cmds.py +22 -0
- mirrorneuron_cli-1.2.5/tests/test_server_cmds.py +2646 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/test_service_cmds.py +17 -1
- mirrorneuron_cli-1.2.5/tests/test_shared.py +242 -0
- mirrorneuron_cli-1.2.5/tests/test_sys_cmds.py +161 -0
- mirrorneuron_cli-1.2.5/tests/test_terminal.py +32 -0
- mirrorneuron_cli-1.2.5/tests/test_ui.py +49 -0
- mirrorneuron_cli-1.2.5/tests/test_update_cmds.py +273 -0
- mirrorneuron_cli-1.2.5/tests/test_workflow_validation.py +95 -0
- mirrorneuron_cli-1.2.5/uv.lock +393 -0
- mirrorneuron_cli-1.1.9/mn_cli/config.py +0 -106
- mirrorneuron_cli-1.1.9/mn_cli/libs/run_manifest.py +0 -522
- mirrorneuron_cli-1.1.9/mn_cli/libs/sys_cmds.py +0 -146
- mirrorneuron_cli-1.1.9/mn_cli/libs/workflow_progress.py +0 -131
- mirrorneuron_cli-1.1.9/mn_cli/server_cmds.py +0 -1929
- mirrorneuron_cli-1.1.9/mn_cli/shared.py +0 -45
- mirrorneuron_cli-1.1.9/tests/test_job_cmds.py +0 -507
- mirrorneuron_cli-1.1.9/tests/test_main.py +0 -58
- mirrorneuron_cli-1.1.9/tests/test_run_helpers.py +0 -291
- mirrorneuron_cli-1.1.9/tests/test_server_cmds.py +0 -1310
- mirrorneuron_cli-1.1.9/tests/test_shared.py +0 -121
- mirrorneuron_cli-1.1.9/tests/test_sys_cmds.py +0 -178
- mirrorneuron_cli-1.1.9/tests/test_update_cmds.py +0 -151
- mirrorneuron_cli-1.1.9/uv.lock +0 -204
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/.github/workflows/ci.yml +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/.github/workflows/release.yml +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/LICENSE +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/RELEASE.md +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mirrorneuron_cli.egg-info/dependency_links.txt +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mirrorneuron_cli.egg-info/entry_points.txt +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mirrorneuron_cli.egg-info/top_level.txt +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/__init__.py +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/banner.py +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/error_handler.py +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/__init__.py +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/libs/blueprint_observability.py +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/mn_cli/logging_config.py +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/scripts/check-release-artifacts.sh +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/scripts/make-release-zip.sh +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/scripts/validate-version-tag.sh +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/setup.cfg +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/conftest.py +0 -0
- {mirrorneuron_cli-1.1.9 → mirrorneuron_cli-1.2.5}/tests/test_blueprint_resources.py +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Environments
|
|
2
2
|
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
3
5
|
.venv
|
|
6
|
+
.venv/
|
|
4
7
|
env/
|
|
5
8
|
venv/
|
|
6
9
|
ENV/
|
|
@@ -30,5 +33,18 @@ __pycache__/
|
|
|
30
33
|
*.py[cod]
|
|
31
34
|
*$py.class
|
|
32
35
|
.pytest_cache/
|
|
36
|
+
.ruff_cache/
|
|
37
|
+
.mypy_cache/
|
|
38
|
+
.pyre/
|
|
39
|
+
.hypothesis/
|
|
40
|
+
.tox/
|
|
41
|
+
.nox/
|
|
33
42
|
.coverage
|
|
34
|
-
|
|
43
|
+
.coverage.*
|
|
44
|
+
coverage.xml
|
|
45
|
+
htmlcov/
|
|
46
|
+
|
|
47
|
+
# Local editor and OS files
|
|
48
|
+
.DS_Store
|
|
49
|
+
.idea/
|
|
50
|
+
.vscode/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11.15
|
|
@@ -7,3 +7,4 @@ Guidance for future coding agents working in this repository.
|
|
|
7
7
|
- Unless the user explicitly asks for a temporary workaround, fix the root cause in the intended layer or contract.
|
|
8
8
|
- Avoid adding fallback paths, compatibility shims, feature flags, or temp solutions that mask a broken primary path.
|
|
9
9
|
- If fallback behavior is already product-specified, keep it narrow, documented, and tested; do not use it to avoid fixing the primary path.
|
|
10
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mirrorneuron-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.5
|
|
4
4
|
Summary: MirrorNeuron CLI
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.11
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: mirrorneuron-python-sdk
|
|
11
|
+
Requires-Dist: jsonschema>=4.25
|
|
11
12
|
Requires-Dist: typer>=0.9.0
|
|
12
13
|
Requires-Dist: rich>=13.0.0
|
|
13
14
|
Dynamic: license-file
|
|
@@ -23,10 +24,10 @@ services installed by `mn-deploy`.
|
|
|
23
24
|
Install locally and run tests:
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
python3 -m venv .venv
|
|
27
|
+
python3.11 -m venv .venv
|
|
27
28
|
. .venv/bin/activate
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
.venv/bin/python -m pip install -e .
|
|
30
|
+
.venv/bin/python -m pytest -q
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
Try the CLI:
|
|
@@ -9,10 +9,10 @@ services installed by `mn-deploy`.
|
|
|
9
9
|
Install locally and run tests:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
python3 -m venv .venv
|
|
12
|
+
python3.11 -m venv .venv
|
|
13
13
|
. .venv/bin/activate
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
.venv/bin/python -m pip install -e .
|
|
15
|
+
.venv/bin/python -m pytest -q
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Try the CLI:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mirrorneuron-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.5
|
|
4
4
|
Summary: MirrorNeuron CLI
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.11
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: mirrorneuron-python-sdk
|
|
11
|
+
Requires-Dist: jsonschema>=4.25
|
|
11
12
|
Requires-Dist: typer>=0.9.0
|
|
12
13
|
Requires-Dist: rich>=13.0.0
|
|
13
14
|
Dynamic: license-file
|
|
@@ -23,10 +24,10 @@ services installed by `mn-deploy`.
|
|
|
23
24
|
Install locally and run tests:
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
python3 -m venv .venv
|
|
27
|
+
python3.11 -m venv .venv
|
|
27
28
|
. .venv/bin/activate
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
.venv/bin/python -m pip install -e .
|
|
30
|
+
.venv/bin/python -m pytest -q
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
Try the CLI:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.gitignore
|
|
2
|
+
.python-version
|
|
2
3
|
AGENTS.md
|
|
3
4
|
LICENSE
|
|
4
5
|
README.md
|
|
@@ -19,27 +20,39 @@ mn_cli/config.py
|
|
|
19
20
|
mn_cli/error_handler.py
|
|
20
21
|
mn_cli/logging_config.py
|
|
21
22
|
mn_cli/main.py
|
|
23
|
+
mn_cli/runtime_mode.py
|
|
24
|
+
mn_cli/runtime_state.py
|
|
25
|
+
mn_cli/sdk_path.py
|
|
22
26
|
mn_cli/server_cmds.py
|
|
23
27
|
mn_cli/shared.py
|
|
28
|
+
mn_cli/terminal.py
|
|
24
29
|
mn_cli/update_cmds.py
|
|
25
30
|
mn_cli/libs/__init__.py
|
|
31
|
+
mn_cli/libs/artifacts.py
|
|
26
32
|
mn_cli/libs/backup_cmds.py
|
|
27
33
|
mn_cli/libs/blueprint_cmds.py
|
|
34
|
+
mn_cli/libs/blueprint_models.py
|
|
28
35
|
mn_cli/libs/blueprint_observability.py
|
|
29
36
|
mn_cli/libs/blueprint_repository.py
|
|
30
37
|
mn_cli/libs/blueprint_resources.py
|
|
38
|
+
mn_cli/libs/bundles.py
|
|
31
39
|
mn_cli/libs/deployment_cmds.py
|
|
32
40
|
mn_cli/libs/event_relay.py
|
|
33
41
|
mn_cli/libs/job_cmds.py
|
|
42
|
+
mn_cli/libs/model_cmds.py
|
|
34
43
|
mn_cli/libs/resource_cmds.py
|
|
35
44
|
mn_cli/libs/run_cmds.py
|
|
36
45
|
mn_cli/libs/run_logs.py
|
|
37
46
|
mn_cli/libs/run_manifest.py
|
|
47
|
+
mn_cli/libs/runtime_health.py
|
|
38
48
|
mn_cli/libs/schedule_cmds.py
|
|
39
49
|
mn_cli/libs/service_cmds.py
|
|
50
|
+
mn_cli/libs/skill_runtime.py
|
|
40
51
|
mn_cli/libs/sys_cmds.py
|
|
41
52
|
mn_cli/libs/ui.py
|
|
42
53
|
mn_cli/libs/workflow_progress.py
|
|
54
|
+
mn_cli/libs/workflow_validation.py
|
|
55
|
+
mn_cli/schemas/workflow_manifest.schema.json
|
|
43
56
|
scripts/check-release-artifacts.sh
|
|
44
57
|
scripts/make-release-zip.sh
|
|
45
58
|
scripts/validate-version-tag.sh
|
|
@@ -49,13 +62,22 @@ tests/test_blueprint_cmds.py
|
|
|
49
62
|
tests/test_blueprint_repository.py
|
|
50
63
|
tests/test_blueprint_resources.py
|
|
51
64
|
tests/test_deployment_cmds.py
|
|
65
|
+
tests/test_docker_network_integration.py
|
|
52
66
|
tests/test_job_cmds.py
|
|
53
67
|
tests/test_main.py
|
|
68
|
+
tests/test_model_cmds.py
|
|
69
|
+
tests/test_resource_cmds.py
|
|
54
70
|
tests/test_run_cmds.py
|
|
55
71
|
tests/test_run_helpers.py
|
|
72
|
+
tests/test_runtime_health.py
|
|
73
|
+
tests/test_runtime_mode.py
|
|
74
|
+
tests/test_runtime_state.py
|
|
56
75
|
tests/test_schedule_cmds.py
|
|
57
76
|
tests/test_server_cmds.py
|
|
58
77
|
tests/test_service_cmds.py
|
|
59
78
|
tests/test_shared.py
|
|
60
79
|
tests/test_sys_cmds.py
|
|
61
|
-
tests/
|
|
80
|
+
tests/test_terminal.py
|
|
81
|
+
tests/test_ui.py
|
|
82
|
+
tests/test_update_cmds.py
|
|
83
|
+
tests/test_workflow_validation.py
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mn_cli.sdk_path import add_local_sdk_path
|
|
8
|
+
|
|
9
|
+
add_local_sdk_path("runtime_config.py")
|
|
10
|
+
|
|
11
|
+
from mn_sdk.runtime_config import RuntimeConfig, default_logs_root
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class CliConfig:
|
|
16
|
+
grpc_target: str = "localhost:55051"
|
|
17
|
+
grpc_timeout_seconds: float | None = 10.0
|
|
18
|
+
grpc_auth_token: str = ""
|
|
19
|
+
grpc_admin_token: str = ""
|
|
20
|
+
log_path: Path = default_logs_root() / "cli.log"
|
|
21
|
+
output_mode: str = "rich"
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_env(cls) -> "CliConfig":
|
|
25
|
+
runtime_config = RuntimeConfig.from_env()
|
|
26
|
+
return cls(
|
|
27
|
+
grpc_target=runtime_config.grpc_target,
|
|
28
|
+
grpc_timeout_seconds=runtime_config.grpc_timeout_seconds,
|
|
29
|
+
grpc_auth_token=runtime_config.grpc_auth_token,
|
|
30
|
+
grpc_admin_token=runtime_config.grpc_admin_token,
|
|
31
|
+
log_path=Path(
|
|
32
|
+
os.getenv(
|
|
33
|
+
"MN_CLI_LOG_PATH",
|
|
34
|
+
str(default_logs_root() / "cli.log"),
|
|
35
|
+
)
|
|
36
|
+
).expanduser(),
|
|
37
|
+
output_mode=os.getenv("MN_CLI_OUTPUT", "rich"),
|
|
38
|
+
)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import mimetypes
|
|
5
|
+
import os
|
|
6
|
+
import socket
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mn_sdk.runtime_config import resolve_mn_home
|
|
11
|
+
|
|
12
|
+
from mn_cli.runtime_state import read_env_file
|
|
13
|
+
|
|
14
|
+
DEFAULT_INLINE_PAYLOAD_MAX_BYTES = 1_048_576
|
|
15
|
+
DEFAULT_ARTIFACT_PORT = "55660"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def promote_large_payloads_to_blob_refs(
|
|
19
|
+
manifest: dict[str, Any],
|
|
20
|
+
payloads: dict[str, bytes],
|
|
21
|
+
*,
|
|
22
|
+
runtime_env: dict[str, str] | None = None,
|
|
23
|
+
) -> list[dict[str, Any]]:
|
|
24
|
+
threshold = _inline_payload_max_bytes()
|
|
25
|
+
if threshold < 0:
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
env = _runtime_env_file_values()
|
|
29
|
+
env.update(os.environ)
|
|
30
|
+
env.update(runtime_env or {})
|
|
31
|
+
root = _host_blob_store_root(env)
|
|
32
|
+
promoted: list[dict[str, Any]] = []
|
|
33
|
+
|
|
34
|
+
for rel_path, contents in list(payloads.items()):
|
|
35
|
+
if len(contents) <= threshold:
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
blob_ref = _store_payload_blob(root, rel_path, contents, env)
|
|
39
|
+
promoted.append(blob_ref)
|
|
40
|
+
del payloads[rel_path]
|
|
41
|
+
|
|
42
|
+
if promoted:
|
|
43
|
+
metadata = manifest.setdefault("metadata", {})
|
|
44
|
+
artifacts = metadata.setdefault("mn_artifacts", {})
|
|
45
|
+
artifacts.setdefault("blob_refs", []).extend(promoted)
|
|
46
|
+
|
|
47
|
+
return promoted
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _store_payload_blob(
|
|
51
|
+
root: Path,
|
|
52
|
+
rel_path: str,
|
|
53
|
+
contents: bytes,
|
|
54
|
+
env: dict[str, str],
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
sha256 = hashlib.sha256(contents).hexdigest()
|
|
57
|
+
target = root / sha256[:2] / sha256
|
|
58
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
|
|
60
|
+
if not target.exists():
|
|
61
|
+
tmp = target.with_name(f"{target.name}.tmp-{os.getpid()}")
|
|
62
|
+
tmp.write_bytes(contents)
|
|
63
|
+
os.replace(tmp, target)
|
|
64
|
+
|
|
65
|
+
media_type, _encoding = mimetypes.guess_type(rel_path)
|
|
66
|
+
location = _blob_location(sha256, env)
|
|
67
|
+
|
|
68
|
+
blob_ref: dict[str, Any] = {
|
|
69
|
+
"type": "blob_ref",
|
|
70
|
+
"sha256": sha256,
|
|
71
|
+
"size_bytes": len(contents),
|
|
72
|
+
"media_type": media_type or "application/octet-stream",
|
|
73
|
+
"logical_name": Path(rel_path).name,
|
|
74
|
+
"scope": "job",
|
|
75
|
+
"payload_path": rel_path.replace("\\", "/"),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if location:
|
|
79
|
+
blob_ref["locations"] = [location]
|
|
80
|
+
|
|
81
|
+
return blob_ref
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _blob_location(sha256: str, env: dict[str, str]) -> dict[str, str] | None:
|
|
85
|
+
base_url = str(env.get("MN_ARTIFACT_ADVERTISE_URL") or os.getenv("MN_ARTIFACT_ADVERTISE_URL") or "").strip()
|
|
86
|
+
if not base_url:
|
|
87
|
+
host = (
|
|
88
|
+
str(env.get("MN_NETWORK_ADVERTISE_HOST") or os.getenv("MN_NETWORK_ADVERTISE_HOST") or "").strip()
|
|
89
|
+
or _detect_lan_ip()
|
|
90
|
+
)
|
|
91
|
+
port = str(env.get("MN_ARTIFACT_PORT") or os.getenv("MN_ARTIFACT_PORT") or DEFAULT_ARTIFACT_PORT).strip()
|
|
92
|
+
if not host or not port:
|
|
93
|
+
return None
|
|
94
|
+
base_url = f"http://{host}:{port}"
|
|
95
|
+
|
|
96
|
+
location = {
|
|
97
|
+
"url": f"{base_url.rstrip('/')}/blobs/{sha256}",
|
|
98
|
+
"status": "available",
|
|
99
|
+
}
|
|
100
|
+
node = str(env.get("MN_NODE_NAME") or os.getenv("MN_NODE_NAME") or "").strip()
|
|
101
|
+
if node:
|
|
102
|
+
location["node"] = node
|
|
103
|
+
return location
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _host_blob_store_root(env: dict[str, str]) -> Path:
|
|
107
|
+
configured = (
|
|
108
|
+
env.get("MN_HOST_BLOB_STORE_DIR")
|
|
109
|
+
or os.getenv("MN_HOST_BLOB_STORE_DIR")
|
|
110
|
+
or env.get("MN_BLOB_STORE_ROOT")
|
|
111
|
+
or os.getenv("MN_BLOB_STORE_ROOT")
|
|
112
|
+
)
|
|
113
|
+
if configured:
|
|
114
|
+
return Path(configured).expanduser()
|
|
115
|
+
return resolve_mn_home() / "blobs"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _runtime_env_file_values() -> dict[str, str]:
|
|
119
|
+
env_file = resolve_mn_home() / "docker-compose.env"
|
|
120
|
+
return {key.strip(): value.strip() for key, value in read_env_file(env_file).items()}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _inline_payload_max_bytes() -> int:
|
|
124
|
+
value = os.getenv("MN_INLINE_PAYLOAD_MAX_BYTES", str(DEFAULT_INLINE_PAYLOAD_MAX_BYTES))
|
|
125
|
+
try:
|
|
126
|
+
return int(value)
|
|
127
|
+
except (TypeError, ValueError):
|
|
128
|
+
return DEFAULT_INLINE_PAYLOAD_MAX_BYTES
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _detect_lan_ip() -> str:
|
|
132
|
+
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
133
|
+
try:
|
|
134
|
+
probe.connect(("8.8.8.8", 80))
|
|
135
|
+
return probe.getsockname()[0]
|
|
136
|
+
except OSError:
|
|
137
|
+
return "127.0.0.1"
|
|
138
|
+
finally:
|
|
139
|
+
probe.close()
|
|
@@ -12,8 +12,11 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
import typer
|
|
14
14
|
|
|
15
|
+
from mn_sdk.runtime_config import default_runs_root
|
|
16
|
+
|
|
15
17
|
from mn_cli.error_handler import handle_cli_error
|
|
16
18
|
from mn_cli.libs.blueprint_observability import make_blueprint_run_id
|
|
19
|
+
from mn_cli.libs.ui import print_success_confirmation
|
|
17
20
|
from mn_cli.shared import client, console, logger
|
|
18
21
|
|
|
19
22
|
SCHEMA_VERSION = "mn.backup.v1"
|
|
@@ -69,8 +72,12 @@ def backup(
|
|
|
69
72
|
_merge_cli_source_metadata(backup_payload, target)
|
|
70
73
|
|
|
71
74
|
archive_path = _write_backup_archive(backup_payload, bundle_files, output, target)
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
print_success_confirmation(
|
|
76
|
+
console,
|
|
77
|
+
"Job backup",
|
|
78
|
+
details=[("Backup", archive_path), ("Source job", job_id)],
|
|
79
|
+
next_steps=f"mn job restore <blueprint-id> --input {archive_path}",
|
|
80
|
+
)
|
|
74
81
|
except BackupRestoreError as exc:
|
|
75
82
|
console.print(f"[red]{exc}[/red]")
|
|
76
83
|
raise typer.Exit(1)
|
|
@@ -119,12 +126,17 @@ def restore(
|
|
|
119
126
|
knowledge_files,
|
|
120
127
|
)
|
|
121
128
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
print_success_confirmation(
|
|
130
|
+
console,
|
|
131
|
+
"Job restore",
|
|
132
|
+
status="paused",
|
|
133
|
+
details=[
|
|
134
|
+
("Job ID", new_job_id),
|
|
135
|
+
("Run ID", new_run_id),
|
|
136
|
+
("Original job", result.get("source_job_id", "unknown")),
|
|
137
|
+
],
|
|
138
|
+
next_steps=f"mn job resume {new_job_id}",
|
|
126
139
|
)
|
|
127
|
-
console.print(f"Resume when ready: [bold]mn job resume {new_job_id}[/bold]")
|
|
128
140
|
except BackupRestoreError as exc:
|
|
129
141
|
console.print(f"[red]{exc}[/red]")
|
|
130
142
|
raise typer.Exit(1)
|
|
@@ -228,7 +240,7 @@ def _job_from_get_job_payload(payload: Any) -> dict[str, Any]:
|
|
|
228
240
|
|
|
229
241
|
|
|
230
242
|
def _runs_root() -> Path:
|
|
231
|
-
return
|
|
243
|
+
return default_runs_root()
|
|
232
244
|
|
|
233
245
|
|
|
234
246
|
def _load_run_record(run_id: str) -> dict[str, Any] | None:
|