zenthrix 0.1.2__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.
Files changed (40) hide show
  1. {zenthrix-0.1.2 → zenthrix-0.2.0}/CHANGELOG.md +30 -1
  2. {zenthrix-0.1.2 → zenthrix-0.2.0}/PKG-INFO +29 -4
  3. {zenthrix-0.1.2 → zenthrix-0.2.0}/README.md +27 -3
  4. {zenthrix-0.1.2 → zenthrix-0.2.0}/pyproject.toml +4 -2
  5. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/__init__.py +4 -1
  6. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/adapters/__init__.py +7 -1
  7. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/adapters/gguf_loader.py +2 -2
  8. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/adapters/onnx_loader.py +2 -1
  9. zenthrix-0.2.0/python/zenthrix/adapters/safetensors_loader.py +16 -0
  10. zenthrix-0.2.0/python/zenthrix/cli.py +227 -0
  11. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/engine.py +15 -5
  12. zenthrix-0.2.0/python/zenthrix/metadata.py +342 -0
  13. zenthrix-0.2.0/python/zenthrix/runtime.py +96 -0
  14. zenthrix-0.2.0/python/zenthrix/settings.py +103 -0
  15. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/validation.py +3 -1
  16. zenthrix-0.2.0/tests/test_cli.py +129 -0
  17. {zenthrix-0.1.2 → zenthrix-0.2.0}/tests/test_engine.py +23 -0
  18. zenthrix-0.2.0/tests/test_metadata.py +185 -0
  19. zenthrix-0.2.0/tests/test_runtime.py +92 -0
  20. zenthrix-0.2.0/tests/test_settings.py +99 -0
  21. {zenthrix-0.1.2 → zenthrix-0.2.0}/tests/test_validation.py +26 -2
  22. {zenthrix-0.1.2 → zenthrix-0.2.0}/uv.lock +1 -1
  23. zenthrix-0.1.2/python/zenthrix/cli.py +0 -84
  24. zenthrix-0.1.2/tests/test_cli.py +0 -77
  25. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/CODEOWNERS +0 -0
  26. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  27. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  28. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  29. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/SECURITY.md +0 -0
  30. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/SUPPORT.md +0 -0
  31. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/dependabot.yml +0 -0
  32. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/pull_request_template.md +0 -0
  33. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/workflows/ci.yml +0 -0
  34. {zenthrix-0.1.2 → zenthrix-0.2.0}/.github/workflows/release.yml +0 -0
  35. {zenthrix-0.1.2 → zenthrix-0.2.0}/.gitignore +0 -0
  36. {zenthrix-0.1.2 → zenthrix-0.2.0}/CONTRIBUTING.md +0 -0
  37. {zenthrix-0.1.2 → zenthrix-0.2.0}/LICENSE +0 -0
  38. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/adapters/pytorch_loader.py +0 -0
  39. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/config.py +0 -0
  40. {zenthrix-0.1.2 → zenthrix-0.2.0}/python/zenthrix/exceptions.py +0 -0
@@ -5,7 +5,34 @@ 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
- ## [Unreleased]
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
+
24
+ ## [0.1.3] - 2026-09-18
25
+
26
+ ### Changed
27
+
28
+ - Added an injectable runtime adapter boundary so provisioned native runtimes
29
+ can implement inference without changing the public `Engine` API.
30
+ - Added opt-in JSON output for CLI inference results and expected errors.
31
+ - `zenthrix inspect` now returns a non-zero error when the native engine is
32
+ unavailable instead of reporting a successful but incomplete inspection.
33
+ - ONNX and GGUF adapter path errors now use the public
34
+ `InputValidationError` type consistently.
35
+ - Bumped the package and public API version to `0.1.3`.
9
36
 
10
37
  ## [0.1.2] - 2026-09-18
11
38
 
@@ -54,3 +81,5 @@ and this project follows [Semantic Versioning](https://semver.org/).
54
81
  [0.1.0]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.1.0
55
82
  [0.1.1]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.1.1
56
83
  [0.1.2]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.1.2
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.1.2
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
@@ -217,6 +218,7 @@ Description-Content-Type: text/markdown
217
218
  **Hardware-Adaptive Edge Neural Graph Compiler**
218
219
 
219
220
  [![PyPI](https://img.shields.io/badge/pypi-zenthrix-blue)](https://pypi.org/project/zenthrix/)
221
+ [![Release](https://img.shields.io/github/v/release/withbrian-technologies/zenthrix?label=release)](https://github.com/withbrian-technologies/zenthrix/releases/latest)
220
222
  [![License](https://img.shields.io/badge/license-Apache%202.0-green)](LICENSE)
221
223
  [![CI](https://img.shields.io/badge/CI-passing-brightgreen)](.github/workflows/ci.yml)
222
224
 
@@ -238,7 +240,7 @@ Zenthrix is an edge-native model compiler frontend for compiling open-weight neu
238
240
 
239
241
  ## Key Features
240
242
 
241
- - **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.
242
244
  - **Unified Memory Tiling** — Schedules compute passes against unified memory architectures, reducing peak active RAM allocation by up to 40%.
243
245
  - **Zero-Copy Runtime** — Emits standalone, relocatable `.zx` binaries that execute locally without a heavy Python runtime dependency.
244
246
  - **Privacy-First Compilation** — Models compile entirely on-device; weights and computational graphs never leave the local environment.
@@ -290,9 +292,16 @@ zenthrix compile \
290
292
  --output ./llama-3.2-1b.zx
291
293
  ```
292
294
 
293
- ### 2. Inspect Graph Optimizations
295
+ ### 2. Inspect Models & Graph Optimizations
294
296
 
295
- Analyze operator fusions and projected memory footprints prior to compilation:
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:
296
305
 
297
306
  ```bash
298
307
  zenthrix inspect ./llama-3.2-1b.zx --memory-profile
@@ -329,6 +338,22 @@ print(f"Time to First Token (TTFT): {output.ttft_ms} ms")
329
338
  print(f"Throughput: {output.tokens_per_second} tokens/sec")
330
339
  ```
331
340
 
341
+ ### Automation and Runtime Availability
342
+
343
+ The public package does not include the proprietary native compiler/runtime.
344
+ `compile`, `inspect`, and `run` therefore return a non-zero status until a
345
+ compatible runtime is provisioned; they never create placeholder artifacts or
346
+ claim successful inference.
347
+
348
+ For automation, add `--json` to `run`, `inspect`, or `compile`. Expected
349
+ failures are emitted as a JSON object with `error` and `message` fields:
350
+
351
+ ```json
352
+ {"error": "EngineUnavailableError", "message": "..."}
353
+ ```
354
+
355
+ Successful inference uses `text`, `ttft_ms`, and `tokens_per_second` fields.
356
+
332
357
  ## Supported Target Architectures
333
358
 
334
359
  | Silicon Target | Optimization Backend | Compute Units |
@@ -3,6 +3,7 @@
3
3
  **Hardware-Adaptive Edge Neural Graph Compiler**
4
4
 
5
5
  [![PyPI](https://img.shields.io/badge/pypi-zenthrix-blue)](https://pypi.org/project/zenthrix/)
6
+ [![Release](https://img.shields.io/github/v/release/withbrian-technologies/zenthrix?label=release)](https://github.com/withbrian-technologies/zenthrix/releases/latest)
6
7
  [![License](https://img.shields.io/badge/license-Apache%202.0-green)](LICENSE)
7
8
  [![CI](https://img.shields.io/badge/CI-passing-brightgreen)](.github/workflows/ci.yml)
8
9
 
@@ -24,7 +25,7 @@ Zenthrix is an edge-native model compiler frontend for compiling open-weight neu
24
25
 
25
26
  ## Key Features
26
27
 
27
- - **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.
28
29
  - **Unified Memory Tiling** — Schedules compute passes against unified memory architectures, reducing peak active RAM allocation by up to 40%.
29
30
  - **Zero-Copy Runtime** — Emits standalone, relocatable `.zx` binaries that execute locally without a heavy Python runtime dependency.
30
31
  - **Privacy-First Compilation** — Models compile entirely on-device; weights and computational graphs never leave the local environment.
@@ -76,9 +77,16 @@ zenthrix compile \
76
77
  --output ./llama-3.2-1b.zx
77
78
  ```
78
79
 
79
- ### 2. Inspect Graph Optimizations
80
+ ### 2. Inspect Models & Graph Optimizations
80
81
 
81
- Analyze operator fusions and projected memory footprints prior to compilation:
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:
82
90
 
83
91
  ```bash
84
92
  zenthrix inspect ./llama-3.2-1b.zx --memory-profile
@@ -115,6 +123,22 @@ print(f"Time to First Token (TTFT): {output.ttft_ms} ms")
115
123
  print(f"Throughput: {output.tokens_per_second} tokens/sec")
116
124
  ```
117
125
 
126
+ ### Automation and Runtime Availability
127
+
128
+ The public package does not include the proprietary native compiler/runtime.
129
+ `compile`, `inspect`, and `run` therefore return a non-zero status until a
130
+ compatible runtime is provisioned; they never create placeholder artifacts or
131
+ claim successful inference.
132
+
133
+ For automation, add `--json` to `run`, `inspect`, or `compile`. Expected
134
+ failures are emitted as a JSON object with `error` and `message` fields:
135
+
136
+ ```json
137
+ {"error": "EngineUnavailableError", "message": "..."}
138
+ ```
139
+
140
+ Successful inference uses `text`, `ttft_ms`, and `tokens_per_second` fields.
141
+
118
142
  ## Supported Target Architectures
119
143
 
120
144
  | Silicon Target | Optimization Backend | Compute Units |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "zenthrix"
7
- version = "0.1.2"
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.1.2"
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__ = ["validate_gguf", "validate_onnx", "validate_pytorch_export"]
8
+ __all__ = [
9
+ "validate_gguf",
10
+ "validate_onnx",
11
+ "validate_pytorch_export",
12
+ "validate_safetensors",
13
+ ]
8
14
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
+ from ..exceptions import InputValidationError
5
6
  from ..validation import validate_model_path
6
7
 
7
8
 
@@ -9,6 +10,5 @@ def validate_gguf(path: str | Path) -> Path:
9
10
  """Validate a GGUF model path."""
10
11
  model_path = validate_model_path(path)
11
12
  if model_path.suffix.lower() != ".gguf":
12
- raise ValueError(f"Expected a .gguf file, got: {model_path}")
13
+ raise InputValidationError(f"Expected a .gguf file, got: {model_path}")
13
14
  return model_path
14
-
@@ -2,6 +2,7 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
+ from ..exceptions import InputValidationError
5
6
  from ..validation import validate_model_path
6
7
 
7
8
 
@@ -9,5 +10,5 @@ def validate_onnx(path: str | Path) -> Path:
9
10
  """Validate an ONNX model path without importing optional ONNX tooling."""
10
11
  model_path = validate_model_path(path)
11
12
  if model_path.suffix.lower() != ".onnx":
12
- raise ValueError(f"Expected an .onnx file, got: {model_path}")
13
+ raise InputValidationError(f"Expected an .onnx file, got: {model_path}")
13
14
  return model_path
@@ -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())
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
  from math import isfinite
5
5
  from pathlib import Path
6
6
 
7
- from .exceptions import EngineUnavailableError
7
+ from .runtime import RuntimeAdapter, default_runtime
8
8
  from .validation import validate_compiled_model_path
9
9
 
10
10
 
@@ -25,8 +25,11 @@ class Engine:
25
25
  until that engine is provisioned.
26
26
  """
27
27
 
28
- def __init__(self, model_path: str | Path) -> None:
28
+ def __init__(
29
+ self, model_path: str | Path, *, runtime: RuntimeAdapter | None = None
30
+ ) -> None:
29
31
  self.model_path = validate_compiled_model_path(model_path)
32
+ self._runtime = runtime if runtime is not None else default_runtime()
30
33
 
31
34
  def generate(
32
35
  self,
@@ -42,7 +45,14 @@ class Engine:
42
45
  raise ValueError("temperature must be a finite non-negative number")
43
46
  if max_tokens < 1:
44
47
  raise ValueError("max_tokens must be at least 1")
45
- raise EngineUnavailableError(
46
- "The Zenthrix native engine is not installed. "
47
- "Install the platform runtime before calling Engine.generate()."
48
+ result = self._runtime.generate(
49
+ self.model_path,
50
+ prompt,
51
+ temperature=temperature,
52
+ max_tokens=max_tokens,
53
+ )
54
+ return InferenceResult(
55
+ text=result.text,
56
+ ttft_ms=result.ttft_ms,
57
+ tokens_per_second=result.tokens_per_second,
48
58
  )