diff --git a/PUBLIC-EXPORT-MANIFEST.json b/PUBLIC-EXPORT-MANIFEST.json index 24da4e3..0044b7c 100644 --- a/PUBLIC-EXPORT-MANIFEST.json +++ b/PUBLIC-EXPORT-MANIFEST.json @@ -1,5 +1,5 @@ { - "fileCount": 817, + "fileCount": 818, "files": [ { "mode": "644", @@ -129,7 +129,7 @@ { "mode": "644", "path": "README.md", - "sha256": "bbd9eaf054e2b410ab51ba9e044a9bfbccb6a24b6ef1430bd7afb84adb35190a" + "sha256": "191c7f8726bad2524ef718924f2c9839b1d371d42f9393704ea6a16d81dae14e" }, { "mode": "644", @@ -604,7 +604,7 @@ { "mode": "644", "path": "examples/README.md", - "sha256": "48b5631d176c0a2102c01201d10144154624672eaf53096373e5335caba18e11" + "sha256": "2524b7193da3804fafc8c527739c42fd9e7cdb9bc942ed821670c6e0b1cae6ce" }, { "mode": "644", @@ -659,7 +659,7 @@ { "mode": "644", "path": "runtimes/python/README.md", - "sha256": "2951f22ee69530a85f486dfd53e32408a0ae5c1cfbfef8fead446f3c47debc0e" + "sha256": "fb3358a624bfe57f27b4b1b04c7db9ec36be375d847b92c0b7c4d832ed9d585f" }, { "mode": "644", @@ -679,7 +679,7 @@ { "mode": "644", "path": "runtimes/python/pyproject.toml", - "sha256": "f1cee92bb54f219014ec84bcd167ba9d0c32231c517968a90bededf7e1490e99" + "sha256": "525a7e612bf44aafaa29df37ce29e4f0191a3c45a29191523746cd4322b1a70b" }, { "mode": "644", @@ -979,7 +979,7 @@ { "mode": "644", "path": "runtimes/python/src/vyral_runtime/host/cli.py", - "sha256": "1040cf0cecdd429197662cf2365047787ebbcf6b3faf532e8849cebdcac1a609" + "sha256": "a4216db3116918da043902525d84a8a73a1d9563e016b933310818636de813f0" }, { "mode": "644", @@ -1194,7 +1194,7 @@ { "mode": "644", "path": "runtimes/python/tests/test_host_cli.py", - "sha256": "c6292c36a7b9c7d5c03f55c4a46b72d7489abee54f3011dad1a93a01b36024e2" + "sha256": "38776f48036cd936a80a95e99a71361e290362d03c79caa37ad05c68243731cc" }, { "mode": "644", @@ -1509,7 +1509,7 @@ { "mode": "755", "path": "scripts/test-built-sdk-python-runtime.sh", - "sha256": "fef6f538284bef212589dfb6f36aa9864b93325a54ed0a6dd3773e9787e383eb" + "sha256": "4ffe5e55952ea71f8fa32005a123a325d8c41f1dbe5e75c41d9291fcd5a69df3" }, { "mode": "755", @@ -1714,7 +1714,7 @@ { "mode": "755", "path": "scripts/verify-python-runtime-mcp-conformance.sh", - "sha256": "226c47e6027c4396779daa94cf47e9a2c9f8dacc266ac707090ef5b0ab2a55ea" + "sha256": "ec5ae1e62448f8a212cd60168a1af3c373ab046646f3692453577e7b23097e29" }, { "mode": "644", @@ -1734,12 +1734,12 @@ { "mode": "755", "path": "scripts/verify-python-runtime.sh", - "sha256": "cb0c27829555b34ab3f7cdd6b21bcbdf7b99b164644d56e6a82c7b33945342b2" + "sha256": "fbd9cd085eff6803debdad5bc52407a85d99fc5f2b9bae3d5b9de167f59b8e63" }, { "mode": "755", "path": "scripts/verify-release-artifacts.sh", - "sha256": "307ac0f94e51e6bac32fb6dfd52eb2f90d3a49f4cbfec845f881b93bf8574588" + "sha256": "37656bb728e094285b4915f11044fea227cc1c3c34d1497ad8cbde425090d0a6" }, { "mode": "755", @@ -1751,6 +1751,11 @@ "path": "scripts/verify-version-policy.py", "sha256": "67f8af6f4a2cbc95c53ac71a61c18482b672e253ab8da36eab6e97f40a9e9f99" }, + { + "mode": "755", + "path": "scripts/vyral", + "sha256": "6e9c31b95f0914c940dd30c9e587241eae45879270dc6fa16d37262bebaac232" + }, { "mode": "755", "path": "scripts/with-local-cuda-libs.sh", @@ -4089,5 +4094,5 @@ ], "schemaVersion": 1, "sourceDirty": false, - "treeSha256": "27bea22ce5088757200df47d504dbeb8bca99315e7a69fb6a631085500f9bab8" + "treeSha256": "c5bb3835cc777c1ecdb5ca506913cd8f386ec756e56fa16af7695e2dd4588df8" } diff --git a/README.md b/README.md index 85e210a..a38aa84 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,34 @@ for SQLite, Postgres/pgvector, Azure, AWS, Google Cloud, Cloudflare, Temporal, and others. Conformance tests and a versioned qualification matrix make adapter behavior explicit instead of assumed. +## Quick start + +From a source checkout, run: + +```bash +./scripts/vyral +``` + +That is the complete local path. It needs only Python 3.10 or newer—no package +installation, .NET SDK, container runtime, cloud account, external service, or +model download. It runs retrieval with citations, admits durable work, closes +and reopens the runtime, and completes the preserved run. State remains visible +under `./.vyral/quickstart`. + +Create a small editable application when you are ready to build: + +```bash +./scripts/vyral init +python ./vyral_app.py +``` + +The installed command is simply `vyral`; `vyral-runtime` remains a compatibility +alias and the distribution name. Use `./scripts/vyral inspect` to explain the +local providers and limitations. Windows users can invoke the source launcher +with `python scripts/vyral`. Manual installation, custom state paths, JSON +output, reset, and server instructions are in the +[Python runtime guide](runtimes/python). + | You need… | Start here | | --- | --- | | Local RAG / retrieval substrate | [Quick start](#quick-start) | @@ -73,71 +101,7 @@ A package existing in the repository is not a production-readiness claim. The [qualification report](qualification/adapter-qualification.json) is the source of truth for recorded evidence. -## Quick start - -The shortest path is the embedded Python runtime. It needs no .NET SDK, -container runtime, cloud account, external service, or model download. From a -source checkout, create an isolated environment and install the runtime: - -```bash -python3 -m venv .venv -. .venv/bin/activate -python -m pip install --editable runtimes/python -``` - -Run the complete local retrieval-and-durable-execution proof: - -```bash -vyral-runtime quickstart -``` - -It ingests a small corpus, prints citation-ready context and the active -model-free local embedding shape, admits an idempotent durable handler before -dispatch, closes the runtime, reopens the same directory, and completes the -same run identity. State stays visible under `./.vyral/quickstart`. Inspect its -providers, topology, readiness, and limitations without rerunning it: - -```bash -vyral-runtime inspect -``` - -When you are ready to own the code, generate the smallest editable application: - -```bash -vyral-runtime init -python ./vyral_app.py -``` - -The generated file uses the concise `@vyral(...)` authoring surface, prints a -durable receipt before dispatch, closes and reopens its local runtime, and then -completes the preserved run. Rerunning it replays the same idempotent run -instead of dispatching duplicate work. The generator never overwrites an -existing path and keeps state beside the file under `.vyral/vyral_app`. -After editing the intended work, increment the visible `RUN_VERSION` to admit a -new idempotent run. `--path` and `--root` remain available when you want -different code or state locations. - -Add `--json` to the quickstart to capture machine-readable first-citation, -durable-receipt, restart-recovery, and completion timings. Clean wheel and -source-distribution qualification generates and runs the editable application -as well as the connected quickstart in isolated environments. It enforces a -five-minute package-install-to-useful-result budget rather than relying on a -synthetic import smoke. - -The quickstart owns only the directory bearing its marker. Its reset command -refuses foreign, symbolic-link, home, filesystem-root, and working-directory -targets: - -```bash -vyral-runtime quickstart --reset -``` - -Package publication is still withheld, so the editable source install above is -the public pre-release path. The deterministic token-hash provider demonstrates -bounded local mechanics and lexical similarity; it is not presented as a -semantic model. - -### Local REST and client examples +## Server and client examples To exercise the server boundary instead, start the .NET host from the repository root: @@ -158,7 +122,7 @@ node examples/javascript/rag-quickstart.mjs Set `VYRAL_URL` to use a different host. More complete retrieval, GraphRAG, AI, and execution examples live in [examples](examples) and [samples](samples). -### Container +## Container The server image is non-root, shell-free, and compatible with a read-only root filesystem. This local command keeps state in a Docker-managed volume: diff --git a/examples/README.md b/examples/README.md index 5d2e45a..11aa15e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,13 +1,10 @@ # Vyral Examples -For the no-server, no-.NET local path, install the embedded Python runtime and -run its connected retrieval and durable-restart experience: +For the no-server, no-.NET local path, run the embedded Python experience +directly from the repository root: ```bash -python3 -m venv .venv -. .venv/bin/activate -python -m pip install --editable runtimes/python -vyral-runtime quickstart +./scripts/vyral ``` The remaining examples exercise the local HTTP boundary intended for Python diff --git a/runtimes/python/README.md b/runtimes/python/README.md index 71774a8..f4ebf5e 100644 --- a/runtimes/python/README.md +++ b/runtimes/python/README.md @@ -55,7 +55,17 @@ silently delegate embedded behavior to .NET or to the existing HTTP client. ## Local single-player experience -From the repository root, install the package into an isolated environment: +From the repository root, the shortest path runs directly from source: + +```bash +./scripts/vyral +``` + +It performs the connected retrieval-and-execution proof described below. No +installation or third-party service is required. Use `python scripts/vyral` on +Windows. + +To install the command into an isolated environment instead: ```bash python3 -m venv .venv @@ -63,10 +73,11 @@ python3 -m venv .venv python -m pip install --editable runtimes/python ``` -Run the connected retrieval-and-execution proof: +The installed command is `vyral`, and running it without arguments performs the +same proof: ```bash -vyral-runtime quickstart +vyral ``` The quickstart creates and ingests a three-document corpus, returns cited hybrid @@ -82,20 +93,20 @@ for local development; its ranking is not a semantic-model quality claim. Inspect the state and its material limitations independently: ```bash -vyral-runtime inspect +vyral inspect ``` The quickstart records an ownership marker and will reset only a dedicated directory bearing that marker: ```bash -vyral-runtime quickstart --reset +vyral quickstart --reset ``` Generate one editable application when you are ready to own the code: ```bash -vyral-runtime init +vyral init python ./vyral_app.py ``` @@ -110,10 +121,10 @@ work to admit a new run. Use `--path` or `--root` when you need non-default code or state locations. Use `--json` with `init`, `quickstart`, or `inspect` for machine-readable -output. Package publication remains withheld, so the editable install is the -supported public pre-release path. Once a qualified wheel is published, the -install command can be replaced with `python -m pip install vyral-runtime` -without changing the local commands. +output. `vyral-runtime` remains a compatibility alias. Package publication is +withheld, so direct source use or the editable install is the public pre-release +path. Once a qualified wheel is published, the install command can be replaced +with `python -m pip install vyral-runtime` without changing the local commands. The quickstart JSON includes measured `firstCitationMs`, `durableReceiptMs`, `restartRecoveryMs`, and `completedMs` milestones. Artifact qualification runs @@ -248,7 +259,7 @@ Install the server extra and choose an explicit durable directory: ```bash python -m pip install "vyral-runtime[server]" export VYRAL_API_KEY="replace-with-a-secret" -vyral-runtime --root ./.vyral --host 127.0.0.1 --port 5220 +vyral serve --root ./.vyral --host 127.0.0.1 --port 5220 ``` REST is available at the public OpenAPI paths and stateless MCP at `/mcp`. diff --git a/runtimes/python/pyproject.toml b/runtimes/python/pyproject.toml index f2bfad5..f53f8ed 100644 --- a/runtimes/python/pyproject.toml +++ b/runtimes/python/pyproject.toml @@ -40,6 +40,7 @@ dev = [ ] [project.scripts] +vyral = "vyral_runtime.host.cli:main" vyral-runtime = "vyral_runtime.host.cli:main" [project.urls] diff --git a/runtimes/python/src/vyral_runtime/host/cli.py b/runtimes/python/src/vyral_runtime/host/cli.py index 5b34a34..65d8c05 100644 --- a/runtimes/python/src/vyral_runtime/host/cli.py +++ b/runtimes/python/src/vyral_runtime/host/cli.py @@ -23,32 +23,39 @@ DEFAULT_QUICKSTART_ROOT = ".vyral/quickstart" -def main(argv: Sequence[str] | None = None) -> int: +def main( + argv: Sequence[str] | None = None, + *, + display_name: str = "vyral", +) -> int: selected = list(sys.argv[1:] if argv is None else argv) + if not selected: + return _quickstart_main([], display_name) if selected and selected[0] == "init": - return _init_main(selected[1:]) + return _init_main(selected[1:], display_name) if selected and selected[0] == "quickstart": - return _quickstart_main(selected[1:]) + return _quickstart_main(selected[1:], display_name) if selected and selected[0] == "inspect": - return _inspect_main(selected[1:]) + return _inspect_main(selected[1:], display_name) if selected and selected[0] == "serve": selected = selected[1:] - return _serve_main(selected) + return _serve_main(selected, display_name) -def _serve_main(argv: Sequence[str]) -> int: +def _serve_main(argv: Sequence[str], display_name: str) -> int: parser = argparse.ArgumentParser( - prog="vyral-runtime", + prog=display_name, formatter_class=argparse.RawDescriptionHelpFormatter, description=( - "Serve the local Vyral Python runtime over REST and " - "stateless MCP." + "Run Vyral locally, inspect durable state, create an editable " + "application, or serve REST and stateless MCP." ), epilog=( "Local single-player commands:\n" - " vyral-runtime quickstart\n" - " vyral-runtime inspect\n" - " vyral-runtime init\n\n" + f" {display_name} # run the local proof\n" + f" {display_name} inspect\n" + f" {display_name} init\n" + f" {display_name} serve --root ./.vyral\n\n" "The explicit 'serve' command is also accepted before the " "server options." ), @@ -193,9 +200,9 @@ def _serve_main(argv: Sequence[str]) -> int: return 0 -def _init_main(argv: Sequence[str]) -> int: +def _init_main(argv: Sequence[str], display_name: str) -> int: parser = argparse.ArgumentParser( - prog="vyral-runtime init", + prog=f"{display_name} init", description=( "Create one editable @vyral application that proves durable " "admission, close/reopen recovery, completion, and idempotent rerun." @@ -231,9 +238,9 @@ def _init_main(argv: Sequence[str]) -> int: return 0 -def _quickstart_main(argv: Sequence[str]) -> int: +def _quickstart_main(argv: Sequence[str], display_name: str) -> int: parser = argparse.ArgumentParser( - prog="vyral-runtime quickstart", + prog=f"{display_name} quickstart", description=( "Run cited local retrieval and prove that one idempotently " "admitted execution survives a close/reopen boundary." @@ -284,23 +291,25 @@ def _quickstart_main(argv: Sequence[str]) -> int: print(result.context_text) print("\nInspect this state:") if result_root == default_root: - print(" vyral-runtime inspect") + print(f" {display_name} inspect") else: - print(f" vyral-runtime inspect --root {result.root_path}") + print( + f" {display_name} inspect --root {result.root_path}" + ) print("Reset only this quickstart-owned directory:") if result_root == default_root: - print(" vyral-runtime quickstart --reset") + print(f" {display_name} quickstart --reset") else: print( - " vyral-runtime quickstart " + f" {display_name} quickstart " f"--root {result.root_path} --reset" ) return 0 -def _inspect_main(argv: Sequence[str]) -> int: +def _inspect_main(argv: Sequence[str], display_name: str) -> int: parser = argparse.ArgumentParser( - prog="vyral-runtime inspect", + prog=f"{display_name} inspect", description=( "Explain the active providers, topology, readiness, and " "limitations for an existing local runtime directory." diff --git a/runtimes/python/tests/test_host_cli.py b/runtimes/python/tests/test_host_cli.py index b9f2f9c..e4947ce 100644 --- a/runtimes/python/tests/test_host_cli.py +++ b/runtimes/python/tests/test_host_cli.py @@ -22,9 +22,9 @@ def test_help_discovers_local_single_player_commands(self) -> None: with redirect_stdout(output), self.assertRaises(SystemExit) as raised: main(["--help"]) self.assertEqual(0, raised.exception.code) - self.assertIn("vyral-runtime init", output.getvalue()) - self.assertIn("vyral-runtime quickstart", output.getvalue()) - self.assertIn("vyral-runtime inspect", output.getvalue()) + self.assertIn("vyral init", output.getvalue()) + self.assertIn("vyral inspect", output.getvalue()) + self.assertIn("vyral # run the local proof", output.getvalue()) def test_init_creates_an_editable_application_without_server_extra( self, @@ -91,12 +91,29 @@ def test_quickstart_uses_the_visible_local_default(self) -> None: self.assertEqual(0, status) run.assert_called_once_with(".vyral/quickstart", emit=print) - self.assertIn(" vyral-runtime inspect\n", output.getvalue()) + self.assertIn(" vyral inspect\n", output.getvalue()) self.assertIn( - " vyral-runtime quickstart --reset\n", + " vyral quickstart --reset\n", output.getvalue(), ) + def test_bare_command_runs_the_local_proof(self) -> None: + result = SimpleNamespace( + root_path=str((Path.cwd() / ".vyral/quickstart").resolve()), + context_text="Context:\nlocal evidence", + to_dict=lambda: {}, + ) + output = StringIO() + with patch( + "vyral_runtime.host.cli.run_local_quickstart_sync", + return_value=result, + ) as run, redirect_stdout(output): + status = main([], display_name="./scripts/vyral") + + self.assertEqual(0, status) + run.assert_called_once_with(".vyral/quickstart", emit=print) + self.assertIn(" ./scripts/vyral inspect\n", output.getvalue()) + def test_inspect_subcommand_summarizes_local_providers(self) -> None: inspection = { "rootPath": "/tmp/vyral-demo", @@ -189,13 +206,6 @@ def test_remote_bind_requires_authentication(self) -> None: main(["--root", root, "--host", "0.0.0.0"]) self.assertEqual(2, raised.exception.code) - def test_root_is_never_selected_implicitly(self) -> None: - with patch.dict( - os.environ, {}, clear=True - ), self.assertRaises(SystemExit) as raised: - main([]) - self.assertEqual(2, raised.exception.code) - def test_port_must_be_in_tcp_range(self) -> None: with tempfile.TemporaryDirectory() as root, patch.dict( os.environ, {}, clear=True diff --git a/scripts/test-built-sdk-python-runtime.sh b/scripts/test-built-sdk-python-runtime.sh index 044e980..fb7fe30 100755 --- a/scripts/test-built-sdk-python-runtime.sh +++ b/scripts/test-built-sdk-python-runtime.sh @@ -54,7 +54,7 @@ python3 -m venv "$TEST_ROOT/venv" env \ VYRAL_API_KEY="$API_KEY" \ - "$TEST_ROOT/venv/bin/vyral-runtime" \ + "$TEST_ROOT/venv/bin/vyral" serve \ --root "$TEST_ROOT/state" \ --host 127.0.0.1 \ --port "$PORT" \ diff --git a/scripts/verify-python-runtime-mcp-conformance.sh b/scripts/verify-python-runtime-mcp-conformance.sh index 834cc70..9f0fc33 100755 --- a/scripts/verify-python-runtime-mcp-conformance.sh +++ b/scripts/verify-python-runtime-mcp-conformance.sh @@ -57,7 +57,7 @@ python3 -m venv "$TEST_ROOT/venv" --disable-pip-version-check \ "${RUNTIME_WHEEL}[server]" -"$TEST_ROOT/venv/bin/vyral-runtime" \ +"$TEST_ROOT/venv/bin/vyral" serve \ --root "$TEST_ROOT/state" \ --host 127.0.0.1 \ --port "$PORT" \ diff --git a/scripts/verify-python-runtime.sh b/scripts/verify-python-runtime.sh index 1d718c2..2898d3f 100755 --- a/scripts/verify-python-runtime.sh +++ b/scripts/verify-python-runtime.sh @@ -26,6 +26,24 @@ python3 -m mypy \ runtimes/python/tests/typecheck_consumer.py \ runtimes/python/tests/test_record_store.py +launcher_root="$work_root/source-launcher" +mkdir -p "$launcher_root" +( + cd "$launcher_root" + "$ROOT/scripts/vyral" >first-run.txt + "$ROOT/scripts/vyral" inspect --json >inspection.json + "$ROOT/scripts/vyral" quickstart --reset >/dev/null +) +if ! grep -q 'Citation-ready context' "$launcher_root/first-run.txt"; then + echo "The source launcher did not produce useful local context." >&2 + exit 1 +fi +if [[ -e "$launcher_root/.vyral/quickstart" ]]; then + echo "The source launcher reset left its owned state directory." >&2 + exit 1 +fi +printf 'python-runtime-source-launcher=ok\n' + python3 -m build \ --sdist \ --wheel \ @@ -76,8 +94,17 @@ with zipfile.ZipFile(wheel_path) as archive: raise SystemExit("Python runtime wheel is missing: " + ", ".join(missing)) if not any(name.endswith(".dist-info/licenses/LICENSE") for name in names): raise SystemExit("Python runtime wheel is missing the Apache license.") - if not any(name.endswith(".dist-info/entry_points.txt") for name in names): + entry_points = [ + name for name in names if name.endswith(".dist-info/entry_points.txt") + ] + if len(entry_points) != 1: raise SystemExit("Python runtime wheel is missing its server entry point.") + console_scripts = archive.read(entry_points[0]).decode("utf-8") + for command in ("vyral =", "vyral-runtime ="): + if command not in console_scripts: + raise SystemExit( + f"Python runtime wheel is missing its {command[:-2]} command." + ) with tarfile.open(sdist_path, "r:gz") as archive: names = {name.split("/", 1)[1] for name in archive.getnames() if "/" in name} @@ -147,10 +174,10 @@ print( PY quickstart_root="$work_root/local-quickstart" -"$work_root/venv/bin/vyral-runtime" quickstart \ +"$work_root/venv/bin/vyral" quickstart \ --root "$quickstart_root" \ --json >"$work_root/quickstart-first.json" -"$work_root/venv/bin/vyral-runtime" inspect \ +"$work_root/venv/bin/vyral" inspect \ --root "$quickstart_root" \ --json >"$work_root/quickstart-inspection.json" "$work_root/venv/bin/python" -m vyral_runtime quickstart \ @@ -202,7 +229,7 @@ print( f"first-citation-ms={first['timings']['firstCitationMs']}" ) PY -"$work_root/venv/bin/vyral-runtime" quickstart \ +"$work_root/venv/bin/vyral" quickstart \ --root "$quickstart_root" \ --reset >/dev/null if [[ -e "$quickstart_root" ]]; then @@ -243,6 +270,7 @@ python3 -m venv "$work_root/server-venv" --quiet \ --disable-pip-version-check \ "${wheel}[server]" +"$work_root/server-venv/bin/vyral" --help >/dev/null "$work_root/server-venv/bin/vyral-runtime" --help >/dev/null "$work_root/server-venv/bin/python" -m vyral_runtime.host --help \ >/dev/null diff --git a/scripts/verify-release-artifacts.sh b/scripts/verify-release-artifacts.sh index a3a9e3a..9c88ab5 100755 --- a/scripts/verify-release-artifacts.sh +++ b/scripts/verify-release-artifacts.sh @@ -424,8 +424,12 @@ with zipfile.ZipFile(wheels[0]) as wheel: ] if len(entry_points) != 1: raise SystemExit("Python runtime wheel is missing its CLI entry point.") - if "vyral-runtime" not in wheel.read(entry_points[0]).decode("utf-8"): - raise SystemExit("Python runtime CLI entry point is malformed.") + console_scripts = wheel.read(entry_points[0]).decode("utf-8") + for command in ("vyral =", "vyral-runtime ="): + if command not in console_scripts: + raise SystemExit( + f"Python runtime CLI entry point is missing: {command[:-2]}" + ) with tarfile.open(sdists[0]) as sdist: names = set(sdist.getnames()) diff --git a/scripts/vyral b/scripts/vyral new file mode 100755 index 0000000..2f1ad63 --- /dev/null +++ b/scripts/vyral @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +"""Run the Vyral Python experience directly from a source checkout.""" + +from __future__ import annotations + +from pathlib import Path +import sys + + +ROOT = Path(__file__).resolve().parent.parent +RUNTIME_SOURCE = ROOT / "runtimes" / "python" / "src" + +if sys.version_info < (3, 10): + raise SystemExit("Vyral requires Python 3.10 or newer.") + +sys.path.insert(0, str(RUNTIME_SOURCE)) + +from vyral_runtime.host.cli import main # noqa: E402 + + +raise SystemExit(main(display_name=sys.argv[0]))