zenthrix 0.1.3__tar.gz → 0.2.0__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.
- {zenthrix-0.1.3 → zenthrix-0.2.0}/CHANGELOG.md +17 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/PKG-INFO +12 -4
- {zenthrix-0.1.3 → zenthrix-0.2.0}/README.md +10 -3
- {zenthrix-0.1.3 → zenthrix-0.2.0}/pyproject.toml +4 -2
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/__init__.py +4 -1
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/adapters/__init__.py +7 -1
- zenthrix-0.2.0/python/zenthrix/adapters/safetensors_loader.py +16 -0
- zenthrix-0.2.0/python/zenthrix/cli.py +227 -0
- zenthrix-0.2.0/python/zenthrix/metadata.py +342 -0
- zenthrix-0.2.0/python/zenthrix/runtime.py +96 -0
- zenthrix-0.2.0/python/zenthrix/settings.py +103 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/validation.py +3 -1
- {zenthrix-0.1.3 → zenthrix-0.2.0}/tests/test_cli.py +34 -5
- zenthrix-0.2.0/tests/test_metadata.py +185 -0
- zenthrix-0.2.0/tests/test_runtime.py +92 -0
- zenthrix-0.2.0/tests/test_settings.py +99 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/tests/test_validation.py +25 -1
- zenthrix-0.1.3/python/zenthrix/cli.py +0 -110
- zenthrix-0.1.3/python/zenthrix/runtime.py +0 -53
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/CODEOWNERS +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/SECURITY.md +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/SUPPORT.md +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/dependabot.yml +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/pull_request_template.md +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/workflows/ci.yml +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.github/workflows/release.yml +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/.gitignore +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/CONTRIBUTING.md +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/LICENSE +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/adapters/gguf_loader.py +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/adapters/onnx_loader.py +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/adapters/pytorch_loader.py +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/config.py +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/engine.py +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/python/zenthrix/exceptions.py +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/tests/test_engine.py +0 -0
- {zenthrix-0.1.3 → zenthrix-0.2.0}/uv.lock +0 -0
|
@@ -5,6 +5,22 @@ All notable changes to Zenthrix are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project follows [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-09-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **SafeTensors format support**: Added `.safetensors` model input format adapter alongside ONNX, GGUF, and PyTorch.
|
|
13
|
+
- **Model metadata inspection**: Extract tensor names, shapes, dtypes, parameter counts, and estimated memory footprint directly from SafeTensors, GGUF, and ONNX source model files without requiring the native engine or loading full weights into RAM.
|
|
14
|
+
- **Configuration file support (`zenthrix.toml`)**: Added project-level defaults for compilation (`target`, `quantization`, `output`) and inference (`max_tokens`, `temperature`) resolved hierarchically with CLI override precedence.
|
|
15
|
+
- **Runtime plugin discovery**: Dynamic discovery of third-party or native runtime adapters registered through the `zenthrix.runtimes` entry point group.
|
|
16
|
+
- **`zenthrix plugins` CLI command**: Inspect and list all discovered runtime plugins in the current Python environment.
|
|
17
|
+
- Exported `ModelMetadata` and `TensorInfo` in the public API.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Bumped the package and public API version to `0.2.0`.
|
|
22
|
+
- Added conditional dependency on `tomli` for Python < 3.11.
|
|
23
|
+
|
|
8
24
|
## [0.1.3] - 2026-09-18
|
|
9
25
|
|
|
10
26
|
### Changed
|
|
@@ -66,3 +82,4 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
66
82
|
[0.1.1]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.1.1
|
|
67
83
|
[0.1.2]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.1.2
|
|
68
84
|
[0.1.3]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.1.3
|
|
85
|
+
[0.2.0]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: zenthrix
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Hardware-adaptive edge neural graph compiler frontend
|
|
5
5
|
Author: WithBrian Technologies
|
|
6
6
|
License: Apache License
|
|
@@ -210,6 +210,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
210
210
|
Classifier: Programming Language :: Python :: 3
|
|
211
211
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
212
212
|
Requires-Python: >=3.10
|
|
213
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
213
214
|
Description-Content-Type: text/markdown
|
|
214
215
|
|
|
215
216
|
# Zenthrix
|
|
@@ -239,7 +240,7 @@ Zenthrix is an edge-native model compiler frontend for compiling open-weight neu
|
|
|
239
240
|
|
|
240
241
|
## Key Features
|
|
241
242
|
|
|
242
|
-
- **Direct Ingestion** — Native loaders for ONNX, PyTorch Export (AOTInductor), and GGUF architectures, with no intermediate format conversion required.
|
|
243
|
+
- **Direct Ingestion** — Native loaders for SafeTensors, ONNX, PyTorch Export (AOTInductor), and GGUF architectures, with no intermediate format conversion required.
|
|
243
244
|
- **Unified Memory Tiling** — Schedules compute passes against unified memory architectures, reducing peak active RAM allocation by up to 40%.
|
|
244
245
|
- **Zero-Copy Runtime** — Emits standalone, relocatable `.zx` binaries that execute locally without a heavy Python runtime dependency.
|
|
245
246
|
- **Privacy-First Compilation** — Models compile entirely on-device; weights and computational graphs never leave the local environment.
|
|
@@ -291,9 +292,16 @@ zenthrix compile \
|
|
|
291
292
|
--output ./llama-3.2-1b.zx
|
|
292
293
|
```
|
|
293
294
|
|
|
294
|
-
### 2. Inspect Graph Optimizations
|
|
295
|
+
### 2. Inspect Models & Graph Optimizations
|
|
295
296
|
|
|
296
|
-
|
|
297
|
+
Inspect uncompiled source models (`.safetensors`, `.gguf`, `.onnx`) directly to view parameter counts, tensor shapes, and estimated memory footprint without requiring the native engine:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
zenthrix inspect ./model.safetensors
|
|
301
|
+
zenthrix inspect ./model.gguf --json
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
For compiled `.zx` binaries, inspect projected memory profiles and operator fusions:
|
|
297
305
|
|
|
298
306
|
```bash
|
|
299
307
|
zenthrix inspect ./llama-3.2-1b.zx --memory-profile
|
|
@@ -25,7 +25,7 @@ Zenthrix is an edge-native model compiler frontend for compiling open-weight neu
|
|
|
25
25
|
|
|
26
26
|
## Key Features
|
|
27
27
|
|
|
28
|
-
- **Direct Ingestion** — Native loaders for ONNX, PyTorch Export (AOTInductor), and GGUF architectures, with no intermediate format conversion required.
|
|
28
|
+
- **Direct Ingestion** — Native loaders for SafeTensors, ONNX, PyTorch Export (AOTInductor), and GGUF architectures, with no intermediate format conversion required.
|
|
29
29
|
- **Unified Memory Tiling** — Schedules compute passes against unified memory architectures, reducing peak active RAM allocation by up to 40%.
|
|
30
30
|
- **Zero-Copy Runtime** — Emits standalone, relocatable `.zx` binaries that execute locally without a heavy Python runtime dependency.
|
|
31
31
|
- **Privacy-First Compilation** — Models compile entirely on-device; weights and computational graphs never leave the local environment.
|
|
@@ -77,9 +77,16 @@ zenthrix compile \
|
|
|
77
77
|
--output ./llama-3.2-1b.zx
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
### 2. Inspect Graph Optimizations
|
|
80
|
+
### 2. Inspect Models & Graph Optimizations
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Inspect uncompiled source models (`.safetensors`, `.gguf`, `.onnx`) directly to view parameter counts, tensor shapes, and estimated memory footprint without requiring the native engine:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
zenthrix inspect ./model.safetensors
|
|
86
|
+
zenthrix inspect ./model.gguf --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For compiled `.zx` binaries, inspect projected memory profiles and operator fusions:
|
|
83
90
|
|
|
84
91
|
```bash
|
|
85
92
|
zenthrix inspect ./llama-3.2-1b.zx --memory-profile
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "zenthrix"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Hardware-adaptive edge neural graph compiler frontend"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -16,7 +16,9 @@ classifiers = [
|
|
|
16
16
|
"Programming Language :: Python :: 3",
|
|
17
17
|
"Programming Language :: Python :: 3 :: Only",
|
|
18
18
|
]
|
|
19
|
-
dependencies = [
|
|
19
|
+
dependencies = [
|
|
20
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
21
|
+
]
|
|
20
22
|
|
|
21
23
|
[dependency-groups]
|
|
22
24
|
dev = [
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
from .engine import Engine, InferenceResult
|
|
4
4
|
from .exceptions import EngineUnavailableError, InputValidationError, ZenthrixError
|
|
5
|
+
from .metadata import ModelMetadata, TensorInfo
|
|
5
6
|
|
|
6
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.2.0"
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"Engine",
|
|
10
11
|
"EngineUnavailableError",
|
|
11
12
|
"InferenceResult",
|
|
12
13
|
"InputValidationError",
|
|
14
|
+
"ModelMetadata",
|
|
15
|
+
"TensorInfo",
|
|
13
16
|
"ZenthrixError",
|
|
14
17
|
"__version__",
|
|
15
18
|
]
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
from .gguf_loader import validate_gguf
|
|
4
4
|
from .onnx_loader import validate_onnx
|
|
5
5
|
from .pytorch_loader import validate_pytorch_export
|
|
6
|
+
from .safetensors_loader import validate_safetensors
|
|
6
7
|
|
|
7
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"validate_gguf",
|
|
10
|
+
"validate_onnx",
|
|
11
|
+
"validate_pytorch_export",
|
|
12
|
+
"validate_safetensors",
|
|
13
|
+
]
|
|
8
14
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""SafeTensors input validation."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ..exceptions import InputValidationError
|
|
6
|
+
from ..validation import validate_model_path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def validate_safetensors(path: str | Path) -> Path:
|
|
10
|
+
"""Validate a SafeTensors model path."""
|
|
11
|
+
model_path = validate_model_path(path)
|
|
12
|
+
if model_path.suffix.lower() != ".safetensors":
|
|
13
|
+
raise InputValidationError(
|
|
14
|
+
f"Expected a .safetensors file, got: {model_path}"
|
|
15
|
+
)
|
|
16
|
+
return model_path
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Command-line interface for the Zenthrix frontend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .metadata import ModelMetadata
|
|
12
|
+
|
|
13
|
+
from . import __version__
|
|
14
|
+
from .engine import Engine
|
|
15
|
+
from .exceptions import EngineUnavailableError, ZenthrixError
|
|
16
|
+
from .validation import (
|
|
17
|
+
validate_compile_options,
|
|
18
|
+
validate_compiled_model_path,
|
|
19
|
+
validate_model_input,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
24
|
+
"""Build the command-line parser."""
|
|
25
|
+
parser = argparse.ArgumentParser(prog="zenthrix")
|
|
26
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
27
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
28
|
+
|
|
29
|
+
compile_parser = commands.add_parser("compile", help="Compile a model")
|
|
30
|
+
compile_parser.add_argument("--model", required=True)
|
|
31
|
+
compile_parser.add_argument("--format", required=True, dest="model_format")
|
|
32
|
+
compile_parser.add_argument("--target", default="auto")
|
|
33
|
+
compile_parser.add_argument("--quantization", default=None)
|
|
34
|
+
compile_parser.add_argument("--output", required=True)
|
|
35
|
+
compile_parser.add_argument("--json", action="store_true")
|
|
36
|
+
|
|
37
|
+
inspect_parser = commands.add_parser(
|
|
38
|
+
"inspect", help="Inspect a compiled or source model"
|
|
39
|
+
)
|
|
40
|
+
inspect_parser.add_argument("model")
|
|
41
|
+
inspect_parser.add_argument("--memory-profile", action="store_true")
|
|
42
|
+
inspect_parser.add_argument("--json", action="store_true")
|
|
43
|
+
|
|
44
|
+
run_parser = commands.add_parser("run", help="Run inference")
|
|
45
|
+
run_parser.add_argument("--model", required=True)
|
|
46
|
+
run_parser.add_argument("--prompt", required=True)
|
|
47
|
+
run_parser.add_argument("--max-tokens", type=int, default=None)
|
|
48
|
+
run_parser.add_argument("--temperature", type=float, default=None)
|
|
49
|
+
run_parser.add_argument("--json", action="store_true")
|
|
50
|
+
|
|
51
|
+
commands.add_parser("plugins", help="List installed runtime plugins")
|
|
52
|
+
|
|
53
|
+
return parser
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _compile(args: argparse.Namespace) -> int:
|
|
57
|
+
validate_model_input(args.model, args.model_format)
|
|
58
|
+
validate_compile_options(args.target, args.quantization, args.output)
|
|
59
|
+
raise ZenthrixError(
|
|
60
|
+
"The native compiler engine is not installed. "
|
|
61
|
+
"The public frontend cannot produce a .zx binary yet."
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _inspect(args: argparse.Namespace) -> int:
|
|
66
|
+
# Source model inspection — works without native engine
|
|
67
|
+
model_path = args.model
|
|
68
|
+
suffix = model_path.rsplit(".", 1)[-1].lower() if "." in model_path else ""
|
|
69
|
+
|
|
70
|
+
if suffix == "zx":
|
|
71
|
+
validate_compiled_model_path(model_path)
|
|
72
|
+
raise EngineUnavailableError(
|
|
73
|
+
"The Zenthrix native engine is not installed. "
|
|
74
|
+
"Install the platform runtime before inspecting compiled models."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Inspect a source model (safetensors, gguf, onnx)
|
|
78
|
+
from .metadata import extract_metadata
|
|
79
|
+
|
|
80
|
+
meta = extract_metadata(model_path)
|
|
81
|
+
|
|
82
|
+
if args.json:
|
|
83
|
+
print(json.dumps(meta.to_dict()))
|
|
84
|
+
else:
|
|
85
|
+
_print_metadata(meta)
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _print_metadata(meta: ModelMetadata) -> None:
|
|
90
|
+
"""Pretty-print model metadata to stdout."""
|
|
91
|
+
|
|
92
|
+
lines = [
|
|
93
|
+
"",
|
|
94
|
+
f" Model: {meta.path}",
|
|
95
|
+
f" Format: {meta.format}",
|
|
96
|
+
f" File size: {_format_size(meta.file_size_bytes)}",
|
|
97
|
+
]
|
|
98
|
+
if meta.parameter_count:
|
|
99
|
+
lines.append(f" Parameters: {meta.parameter_count:,}")
|
|
100
|
+
if meta.estimated_memory_mb:
|
|
101
|
+
lines.append(f" Est. memory: {meta.estimated_memory_mb:.1f} MB")
|
|
102
|
+
if meta.architecture:
|
|
103
|
+
lines.append(f" Architecture: {meta.architecture}")
|
|
104
|
+
if meta.tensors:
|
|
105
|
+
lines.append(f" Tensors: {len(meta.tensors)}")
|
|
106
|
+
if meta.extra:
|
|
107
|
+
for key, value in sorted(meta.extra.items()):
|
|
108
|
+
lines.append(f" {key}: {value}")
|
|
109
|
+
lines.append("")
|
|
110
|
+
print("\n".join(lines))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _format_size(size_bytes: int) -> str:
|
|
114
|
+
"""Format byte count as a human-readable string."""
|
|
115
|
+
if size_bytes >= 1_073_741_824:
|
|
116
|
+
return f"{size_bytes / 1_073_741_824:.1f} GB"
|
|
117
|
+
if size_bytes >= 1_048_576:
|
|
118
|
+
return f"{size_bytes / 1_048_576:.1f} MB"
|
|
119
|
+
if size_bytes >= 1024:
|
|
120
|
+
return f"{size_bytes / 1024:.1f} KB"
|
|
121
|
+
return f"{size_bytes} B"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _run(args: argparse.Namespace) -> int:
|
|
125
|
+
result = Engine(args.model).generate(
|
|
126
|
+
args.prompt,
|
|
127
|
+
temperature=args.temperature if args.temperature is not None else 0.2,
|
|
128
|
+
max_tokens=args.max_tokens if args.max_tokens is not None else 128,
|
|
129
|
+
)
|
|
130
|
+
if args.json:
|
|
131
|
+
print(
|
|
132
|
+
json.dumps(
|
|
133
|
+
{
|
|
134
|
+
"text": result.text,
|
|
135
|
+
"ttft_ms": result.ttft_ms,
|
|
136
|
+
"tokens_per_second": result.tokens_per_second,
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
print(result.text)
|
|
142
|
+
return 0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _plugins(_args: argparse.Namespace) -> int:
|
|
146
|
+
from .runtime import discover_runtimes
|
|
147
|
+
|
|
148
|
+
runtimes = discover_runtimes()
|
|
149
|
+
if not runtimes:
|
|
150
|
+
print("No runtime plugins installed.")
|
|
151
|
+
print()
|
|
152
|
+
print("Install a compatible runtime package, or see:")
|
|
153
|
+
print(" https://github.com/withbrian-technologies/zenthrix#plugins")
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
print("Installed runtimes:")
|
|
157
|
+
for name, adapter_cls in runtimes:
|
|
158
|
+
module = f"{adapter_cls.__module__}:{adapter_cls.__qualname__}"
|
|
159
|
+
print(f" • {name} ({module})")
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main(argv: list[str] | None = None) -> int:
|
|
164
|
+
"""Run the CLI and return a process exit code."""
|
|
165
|
+
args = build_parser().parse_args(argv)
|
|
166
|
+
|
|
167
|
+
# Merge config-file defaults for compile/run when available
|
|
168
|
+
_apply_config_defaults(args)
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
if args.command == "compile":
|
|
172
|
+
return _compile(args)
|
|
173
|
+
if args.command == "inspect":
|
|
174
|
+
return _inspect(args)
|
|
175
|
+
if args.command == "run":
|
|
176
|
+
return _run(args)
|
|
177
|
+
if args.command == "plugins":
|
|
178
|
+
return _plugins(args)
|
|
179
|
+
except (ZenthrixError, ValueError) as error:
|
|
180
|
+
if getattr(args, "json", False):
|
|
181
|
+
print(
|
|
182
|
+
json.dumps(
|
|
183
|
+
{
|
|
184
|
+
"error": type(error).__name__,
|
|
185
|
+
"message": str(error),
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
print(f"zenthrix: error: {error}", file=sys.stderr)
|
|
191
|
+
return 2
|
|
192
|
+
return 0
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _apply_config_defaults(args: argparse.Namespace) -> None:
|
|
196
|
+
"""Load zenthrix.toml and fill in missing CLI arguments."""
|
|
197
|
+
try:
|
|
198
|
+
from .settings import find_config, load_config
|
|
199
|
+
except ImportError:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
config_path = find_config()
|
|
203
|
+
if config_path is None:
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
config = load_config(config_path)
|
|
208
|
+
except Exception:
|
|
209
|
+
return # silently skip broken config during CLI execution
|
|
210
|
+
|
|
211
|
+
command = getattr(args, "command", None)
|
|
212
|
+
section = config.get(command, {}) if command else {}
|
|
213
|
+
if not isinstance(section, dict):
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
for key, value in section.items():
|
|
217
|
+
# CLI arguments take precedence — only fill in None values
|
|
218
|
+
current = getattr(args, key, _SENTINEL)
|
|
219
|
+
if current is None:
|
|
220
|
+
setattr(args, key, value)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
_SENTINEL = object()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == "__main__":
|
|
227
|
+
raise SystemExit(main())
|