diff --git a/.agents/skills/nemoclaw-user-configure-inference/SKILL.md b/.agents/skills/nemoclaw-user-configure-inference/SKILL.md index 63e1a0e72..4e33fa0d2 100644 --- a/.agents/skills/nemoclaw-user-configure-inference/SKILL.md +++ b/.agents/skills/nemoclaw-user-configure-inference/SKILL.md @@ -22,6 +22,22 @@ OpenShell intercepts inference traffic on the host and forwards it to the provid Provider credentials stay on the host. The sandbox does not receive your API key. +## Provider Status + + +| Provider | Status | Endpoint type | Notes | +|----------|--------|---------------|-------| +| NVIDIA Endpoints | Tested | OpenAI-compatible | Hosted models on integrate.api.nvidia.com | +| OpenAI | Tested | Native OpenAI-compatible | Uses OpenAI model IDs | +| Other OpenAI-compatible endpoint | Tested | Custom OpenAI-compatible | For compatible proxies and gateways | +| Anthropic | Tested | Native Anthropic | Uses anthropic-messages | +| Other Anthropic-compatible endpoint | Tested | Custom Anthropic-compatible | For Claude proxies and compatible gateways | +| Google Gemini | Tested | OpenAI-compatible | Uses Google's OpenAI-compatible endpoint | +| Local Ollama | Caveated | Local Ollama API | Available when Ollama is installed or running on the host | +| Local NVIDIA NIM | Experimental | Local OpenAI-compatible | Requires `NEMOCLAW_EXPERIMENTAL=1` and a NIM-capable GPU | +| Local vLLM | Experimental | Local OpenAI-compatible | Requires `NEMOCLAW_EXPERIMENTAL=1` and a server already running on `localhost:8000` | + + ## Provider Options The onboard wizard presents the following provider options by default. @@ -54,15 +70,7 @@ For setup instructions, refer to Use a Local Inference Server (see the `nemoclaw NemoClaw validates the selected provider and model before creating the sandbox. If validation fails, the wizard returns to provider selection. -| Provider type | Validation method | -|---|---| -| OpenAI | Tries `/responses` first, then `/chat/completions`. | -| NVIDIA Endpoints | Tries `/responses` first with a tool-calling probe that matches OpenClaw behavior. Falls back to `/chat/completions` if the endpoint does not return a compatible tool call. | -| Google Gemini | Tries `/responses` first with a tool-calling probe that matches OpenClaw behavior. Falls back to `/chat/completions` if the endpoint does not return a compatible tool call. | -| Other OpenAI-compatible endpoint | Tries `/responses` first with a tool-calling probe that matches OpenClaw behavior. Falls back to `/chat/completions` if the endpoint does not return a compatible tool call. | -| Anthropic-compatible | Tries `/v1/messages`. | -| NVIDIA Endpoints (manual model entry) | Validates the model name against the catalog API. | -| Compatible endpoints | Sends a real inference request because many proxies do not expose a `/models` endpoint. For OpenAI-compatible endpoints, the probe includes tool calling before NemoClaw favors `/responses`. | +*Full details in `references/inference-options.md`.* ## Prerequisites diff --git a/.agents/skills/nemoclaw-user-configure-inference/references/inference-options.md b/.agents/skills/nemoclaw-user-configure-inference/references/inference-options.md index 085ada570..f5ab58374 100644 --- a/.agents/skills/nemoclaw-user-configure-inference/references/inference-options.md +++ b/.agents/skills/nemoclaw-user-configure-inference/references/inference-options.md @@ -13,6 +13,22 @@ OpenShell intercepts inference traffic on the host and forwards it to the provid Provider credentials stay on the host. The sandbox does not receive your API key. +## Provider Status + + +| Provider | Status | Endpoint type | Notes | +|----------|--------|---------------|-------| +| NVIDIA Endpoints | Tested | OpenAI-compatible | Hosted models on integrate.api.nvidia.com | +| OpenAI | Tested | Native OpenAI-compatible | Uses OpenAI model IDs | +| Other OpenAI-compatible endpoint | Tested | Custom OpenAI-compatible | For compatible proxies and gateways | +| Anthropic | Tested | Native Anthropic | Uses anthropic-messages | +| Other Anthropic-compatible endpoint | Tested | Custom Anthropic-compatible | For Claude proxies and compatible gateways | +| Google Gemini | Tested | OpenAI-compatible | Uses Google's OpenAI-compatible endpoint | +| Local Ollama | Caveated | Local Ollama API | Available when Ollama is installed or running on the host | +| Local NVIDIA NIM | Experimental | Local OpenAI-compatible | Requires `NEMOCLAW_EXPERIMENTAL=1` and a NIM-capable GPU | +| Local vLLM | Experimental | Local OpenAI-compatible | Requires `NEMOCLAW_EXPERIMENTAL=1` and a server already running on `localhost:8000` | + + ## Provider Options The onboard wizard presents the following provider options by default. diff --git a/.agents/skills/nemoclaw-user-reference/references/commands.md b/.agents/skills/nemoclaw-user-reference/references/commands.md index 5fb6dadde..814cd8326 100644 --- a/.agents/skills/nemoclaw-user-reference/references/commands.md +++ b/.agents/skills/nemoclaw-user-reference/references/commands.md @@ -83,7 +83,7 @@ Names must follow RFC 1123 subdomain rules: lowercase alphanumeric characters an Uppercase letters are automatically lowercased. Before creating the gateway, the wizard runs preflight checks. -It verifies that Docker is reachable, warns on unsupported runtimes such as Podman, and prints host remediation guidance when prerequisites are missing. +It verifies that Docker is reachable, warns on untested runtimes such as Podman, and prints host remediation guidance when prerequisites are missing. #### `--from ` diff --git a/.agents/skills/nemoclaw-user-reference/references/troubleshooting.md b/.agents/skills/nemoclaw-user-reference/references/troubleshooting.md index bc8b30cd2..5ec77a265 100644 --- a/.agents/skills/nemoclaw-user-reference/references/troubleshooting.md +++ b/.agents/skills/nemoclaw-user-reference/references/troubleshooting.md @@ -16,8 +16,7 @@ Run `source ~/.bashrc` (or `source ~/.zshrc` for zsh), or open a new terminal wi ### Installer fails on unsupported platform The installer checks for a supported OS and architecture before proceeding. -NemoClaw requires Linux Ubuntu 22.04 LTS or later. -If you see an unsupported platform error, verify that you are running on a supported Linux distribution. +If you see an unsupported platform error, verify that you are running on a tested platform listed in the Container Runtimes table in the quickstart guide. ### Node.js version is too old @@ -113,8 +112,8 @@ If onboarding reports that Docker is missing or unreachable, fix Docker first an $ nemoclaw onboard ``` -If you are using Podman, NemoClaw warns and continues, but OpenShell officially documents Docker-based runtimes only. -If onboarding or sandbox lifecycle fails, switch to Docker Desktop, Colima, or Docker Engine and rerun onboarding. +Podman is not a tested runtime. +If onboarding or sandbox lifecycle fails, switch to a tested runtime (Docker Desktop, Colima, or Docker Engine) and rerun onboarding. ### Invalid sandbox name @@ -265,53 +264,6 @@ Use `--follow` to stream logs in real time while debugging. ## Podman -### `open /dev/kmsg: operation not permitted` - -This error appears when the Podman machine is running in rootless mode. -K3s kubelet requires `/dev/kmsg` access for its OOM watcher, which is not available in rootless containers. - -Switch the Podman machine to rootful mode and restart: - -```console -$ podman machine stop -$ podman machine set --rootful -$ podman machine start -``` - -Then destroy and recreate the gateway: - -```console -$ openshell gateway destroy --name nemoclaw -$ nemoclaw onboard -``` - -### Image push timeout with Podman - -When creating a sandbox, the 1.5 GB sandbox image push into K3s may time out through Podman's API socket. -This is a known limitation of the bollard Docker client's default timeout. - -Manually push the image using the Docker CLI, which has no such timeout: - -```console -$ docker images --format '{{.Repository}}:{{.Tag}}' | grep sandbox-from -$ docker save | \ - docker exec -i openshell-cluster-nemoclaw \ - ctr -a /run/k3s/containerd/containerd.sock -n k8s.io images import - -``` - -After the import completes, create the sandbox manually: - -```console -$ openshell sandbox create --name my-assistant --from -``` - -### Podman machine resources - -The default Podman machine has 2 GB RAM, which is insufficient for the sandbox image push and K3s cluster overhead. -Allocate at least 8 GB RAM and 4 CPUs: - -```console -$ podman machine stop -$ podman machine set --cpus 6 --memory 8192 -$ podman machine start -``` +Podman is not a tested runtime. +OpenShell officially documents Docker-based runtimes only. +If you encounter issues with Podman, switch to a tested runtime (Docker Engine, Docker Desktop, or Colima) and rerun onboarding. diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index e4c188c5a..5538dee09 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -48,6 +48,9 @@ jobs: - name: Run basic checks uses: ./.github/actions/basic-checks + - name: Verify platform matrix is in sync + run: python3 scripts/generate-platform-docs.py --check + sandbox-images-and-e2e: needs: [checks, changes] if: needs.changes.outputs.code == 'true' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 633c926ab..884041db7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -71,11 +71,19 @@ repos: # ── Priority 4: regenerate agent skills when docs change ────────────────── - repo: local hooks: + - id: platform-matrix-sync + name: Sync platform matrix to docs + entry: bash -c 'python3 scripts/generate-platform-docs.py && git add README.md docs/get-started/quickstart.md docs/inference/inference-options.md' + language: system + files: ^(ci/platform-matrix\.json|README\.md|docs/get-started/quickstart\.md|docs/inference/inference-options\.md|scripts/generate-platform-docs\.py)$ + pass_filenames: false + priority: 3 + - id: docs-to-skills name: Regenerate agent skills from docs - entry: bash -c 'python3 scripts/docs-to-skills.py docs/ .agents/skills/ --prefix nemoclaw && git add .agents/skills/' + entry: bash -c 'python3 scripts/docs-to-skills.py docs/ .agents/skills/ --prefix nemoclaw-user && git add .agents/skills/' language: system - files: ^docs/.*\.md$ + files: ^(docs/.*\.md$|ci/platform-matrix\.json$|scripts/generate-platform-docs\.py$) pass_filenames: false priority: 4 diff --git a/README.md b/README.md index de18eeeb3..303c6f9ab 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,9 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc | Dependency | Version | |------------|----------------------------------| -| Linux | Ubuntu 22.04 LTS or later | | Node.js | 22.16 or later | | npm | 10 or later | -| Container runtime | Supported runtime installed and running | -| [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed | +| Platform | See below | #### OpenShell Lifecycle @@ -64,13 +62,17 @@ Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway sta #### Container Runtimes -| Platform | Supported runtimes | Notes | -|----------|--------------------|-------| -| Linux | Docker, Podman | Primary supported path. | -| macOS (Apple Silicon) | Colima, Docker Desktop, Podman | Install Xcode Command Line Tools (`xcode-select --install`) and start the runtime before running the installer. | -| macOS (Intel) | Colima, Docker Desktop | Podman on Intel macOS is not yet supported. | -| Windows WSL | Docker Desktop (WSL backend) | Supported target path. | -| DGX Spark | Docker | Use the standard installer and `nemoclaw onboard`. | +The following table lists tested platform and runtime combinations. +Availability is not limited to these entries, but untested configurations may have issues. + + +| OS | Container runtime | Status | Notes | +|----|-------------------|--------|-------| +| Linux | Docker | Tested | Primary tested path. | +| macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | Install Xcode Command Line Tools (`xcode-select --install`) and start the runtime before running the installer. | +| DGX Spark | Docker | Tested | Use the standard installer and `nemoclaw onboard`. | +| Windows WSL2 | Docker Desktop (WSL backend) | Tested with limitations | Requires WSL2 with Docker Desktop backend. | + ### Install NemoClaw and Onboard OpenClaw Agent diff --git a/ci/platform-matrix.json b/ci/platform-matrix.json new file mode 100644 index 000000000..59debfe09 --- /dev/null +++ b/ci/platform-matrix.json @@ -0,0 +1,137 @@ +{ + "$comment": "Single source of truth for tested platforms and providers. Scripts read this to generate README and docs tables. QA/CI update this file; docs are derived.", + "version": "1.0", + "updated": "2026-04-08", + + "statuses": { + "tested": "Validated by CI/QA. Primary path.", + "caveated": "Tested with limitations. See Notes.", + "experimental": "Requires NEMOCLAW_EXPERIMENTAL=1.", + "deferred": "Planned but not yet validated." + }, + + "platforms": [ + { + "name": "Linux", + "runtimes": ["Docker"], + "status": "tested", + "prd_priority": "P0", + "ci_tested": true, + "notes": "Primary tested path." + }, + { + "name": "macOS (Apple Silicon)", + "runtimes": ["Colima", "Docker Desktop"], + "status": "caveated", + "prd_priority": "P0", + "ci_tested": true, + "notes": "Install Xcode Command Line Tools (`xcode-select --install`) and start the runtime before running the installer." + }, + { + "name": "DGX Spark", + "runtimes": ["Docker"], + "status": "tested", + "prd_priority": "P1", + "ci_tested": true, + "notes": "Use the standard installer and `nemoclaw onboard`." + }, + { + "name": "Windows WSL2", + "runtimes": ["Docker Desktop (WSL backend)"], + "status": "caveated", + "prd_priority": "P1", + "ci_tested": false, + "_prd_note": "PRD tracks x86 and ARM (WOA) separately; treating as one entry until ARM is validated independently.", + "notes": "Requires WSL2 with Docker Desktop backend." + }, + { + "name": "DGX Station", + "runtimes": ["Docker"], + "status": "deferred", + "prd_priority": "P1", + "ci_tested": false, + "notes": "P1 per PRD. Setup path not yet validated." + }, + { + "name": "RTX Spark", + "runtimes": ["Docker"], + "status": "deferred", + "prd_priority": "P1", + "ci_tested": false, + "notes": "P1 per PRD. Setup path not yet validated." + }, + { + "name": "NVIDIA RTX", + "runtimes": ["Docker"], + "status": "deferred", + "prd_priority": "P1", + "ci_tested": false, + "notes": "P1 per PRD. Setup path not yet validated." + }, + { + "name": "NVIDIA RTX Pro", + "runtimes": ["Docker"], + "status": "deferred", + "prd_priority": "P1", + "ci_tested": false, + "notes": "P1 per PRD. Setup path not yet validated." + } + ], + + "providers": [ + { + "name": "NVIDIA Endpoints", + "status": "tested", + "endpoint_type": "OpenAI-compatible", + "notes": "Hosted models on integrate.api.nvidia.com" + }, + { + "name": "OpenAI", + "status": "tested", + "endpoint_type": "Native OpenAI-compatible", + "notes": "Uses OpenAI model IDs" + }, + { + "name": "Other OpenAI-compatible endpoint", + "status": "tested", + "endpoint_type": "Custom OpenAI-compatible", + "notes": "For compatible proxies and gateways" + }, + { + "name": "Anthropic", + "status": "tested", + "endpoint_type": "Native Anthropic", + "notes": "Uses anthropic-messages" + }, + { + "name": "Other Anthropic-compatible endpoint", + "status": "tested", + "endpoint_type": "Custom Anthropic-compatible", + "notes": "For Claude proxies and compatible gateways" + }, + { + "name": "Google Gemini", + "status": "tested", + "endpoint_type": "OpenAI-compatible", + "notes": "Uses Google's OpenAI-compatible endpoint" + }, + { + "name": "Local Ollama", + "status": "caveated", + "endpoint_type": "Local Ollama API", + "notes": "Available when Ollama is installed or running on the host" + }, + { + "name": "Local NVIDIA NIM", + "status": "experimental", + "endpoint_type": "Local OpenAI-compatible", + "notes": "Requires `NEMOCLAW_EXPERIMENTAL=1` and a NIM-capable GPU" + }, + { + "name": "Local vLLM", + "status": "experimental", + "endpoint_type": "Local OpenAI-compatible", + "notes": "Requires `NEMOCLAW_EXPERIMENTAL=1` and a server already running on `localhost:8000`" + } + ] +} diff --git a/docs/get-started/quickstart.md b/docs/get-started/quickstart.md index 0102058f0..2858a0dbd 100644 --- a/docs/get-started/quickstart.md +++ b/docs/get-started/quickstart.md @@ -49,11 +49,9 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc | Dependency | Version | |------------|----------------------------------| -| Linux | Ubuntu 22.04 LTS or later | | Node.js | 22.16 or later | | npm | 10 or later | -| Container runtime | Supported runtime installed and running | -| [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed | +| Platform | See below | :::{warning} OpenShell lifecycle For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox. @@ -62,13 +60,17 @@ Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway sta ### Container Runtimes -| Platform | Supported runtimes | Notes | -|----------|--------------------|-------| -| Linux | Docker | Primary supported path. | -| macOS (Apple Silicon) | Colima, Docker Desktop | Install Xcode Command Line Tools (`xcode-select --install`) and start the runtime before running the installer. | -| macOS (Intel) | Docker Desktop | Start the runtime before running the installer. | -| Windows WSL | Docker Desktop (WSL backend) | Supported target path. | -| DGX Spark | Docker | Use the standard installer and `nemoclaw onboard`. | +The following table lists tested platform and runtime combinations. +Availability is not limited to these entries, but untested configurations may have issues. + + +| OS | Container runtime | Status | Notes | +|----|-------------------|--------|-------| +| Linux | Docker | Tested | Primary tested path. | +| macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | Install Xcode Command Line Tools (`xcode-select --install`) and start the runtime before running the installer. | +| DGX Spark | Docker | Tested | Use the standard installer and `nemoclaw onboard`. | +| Windows WSL2 | Docker Desktop (WSL backend) | Tested with limitations | Requires WSL2 with Docker Desktop backend. | + ## Install NemoClaw and Onboard OpenClaw Agent diff --git a/docs/inference/inference-options.md b/docs/inference/inference-options.md index 828dbefdf..4e9133870 100644 --- a/docs/inference/inference-options.md +++ b/docs/inference/inference-options.md @@ -35,6 +35,22 @@ OpenShell intercepts inference traffic on the host and forwards it to the provid Provider credentials stay on the host. The sandbox does not receive your API key. +## Provider Status + + +| Provider | Status | Endpoint type | Notes | +|----------|--------|---------------|-------| +| NVIDIA Endpoints | Tested | OpenAI-compatible | Hosted models on integrate.api.nvidia.com | +| OpenAI | Tested | Native OpenAI-compatible | Uses OpenAI model IDs | +| Other OpenAI-compatible endpoint | Tested | Custom OpenAI-compatible | For compatible proxies and gateways | +| Anthropic | Tested | Native Anthropic | Uses anthropic-messages | +| Other Anthropic-compatible endpoint | Tested | Custom Anthropic-compatible | For Claude proxies and compatible gateways | +| Google Gemini | Tested | OpenAI-compatible | Uses Google's OpenAI-compatible endpoint | +| Local Ollama | Caveated | Local Ollama API | Available when Ollama is installed or running on the host | +| Local NVIDIA NIM | Experimental | Local OpenAI-compatible | Requires `NEMOCLAW_EXPERIMENTAL=1` and a NIM-capable GPU | +| Local vLLM | Experimental | Local OpenAI-compatible | Requires `NEMOCLAW_EXPERIMENTAL=1` and a server already running on `localhost:8000` | + + ## Provider Options The onboard wizard presents the following provider options by default. diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 81050adc9..aafde7699 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -107,7 +107,7 @@ Names must follow RFC 1123 subdomain rules: lowercase alphanumeric characters an Uppercase letters are automatically lowercased. Before creating the gateway, the wizard runs preflight checks. -It verifies that Docker is reachable, warns on unsupported runtimes such as Podman, and prints host remediation guidance when prerequisites are missing. +It verifies that Docker is reachable, warns on untested runtimes such as Podman, and prints host remediation guidance when prerequisites are missing. #### `--from ` diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index c64fe960c..1d91444f9 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -44,8 +44,7 @@ Run `source ~/.bashrc` (or `source ~/.zshrc` for zsh), or open a new terminal wi ### Installer fails on unsupported platform The installer checks for a supported OS and architecture before proceeding. -NemoClaw requires Linux Ubuntu 22.04 LTS or later. -If you see an unsupported platform error, verify that you are running on a supported Linux distribution. +If you see an unsupported platform error, verify that you are running on a tested platform listed in the Container Runtimes table in the quickstart guide. ### Node.js version is too old @@ -141,8 +140,8 @@ If onboarding reports that Docker is missing or unreachable, fix Docker first an $ nemoclaw onboard ``` -If you are using Podman, NemoClaw warns and continues, but OpenShell officially documents Docker-based runtimes only. -If onboarding or sandbox lifecycle fails, switch to Docker Desktop, Colima, or Docker Engine and rerun onboarding. +Podman is not a tested runtime. +If onboarding or sandbox lifecycle fails, switch to a tested runtime (Docker Desktop, Colima, or Docker Engine) and rerun onboarding. ### Invalid sandbox name @@ -297,53 +296,6 @@ Use `--follow` to stream logs in real time while debugging. ## Podman -### `open /dev/kmsg: operation not permitted` - -This error appears when the Podman machine is running in rootless mode. -K3s kubelet requires `/dev/kmsg` access for its OOM watcher, which is not available in rootless containers. - -Switch the Podman machine to rootful mode and restart: - -```console -$ podman machine stop -$ podman machine set --rootful -$ podman machine start -``` - -Then destroy and recreate the gateway: - -```console -$ openshell gateway destroy --name nemoclaw -$ nemoclaw onboard -``` - -### Image push timeout with Podman - -When creating a sandbox, the 1.5 GB sandbox image push into K3s may time out through Podman's API socket. -This is a known limitation of the bollard Docker client's default timeout. - -Manually push the image using the Docker CLI, which has no such timeout: - -```console -$ docker images --format '{{.Repository}}:{{.Tag}}' | grep sandbox-from -$ docker save | \ - docker exec -i openshell-cluster-nemoclaw \ - ctr -a /run/k3s/containerd/containerd.sock -n k8s.io images import - -``` - -After the import completes, create the sandbox manually: - -```console -$ openshell sandbox create --name my-assistant --from -``` - -### Podman machine resources - -The default Podman machine has 2 GB RAM, which is insufficient for the sandbox image push and K3s cluster overhead. -Allocate at least 8 GB RAM and 4 CPUs: - -```console -$ podman machine stop -$ podman machine set --cpus 6 --memory 8192 -$ podman machine start -``` +Podman is not a tested runtime. +OpenShell officially documents Docker-based runtimes only. +If you encounter issues with Podman, switch to a tested runtime (Docker Engine, Docker Desktop, or Colima) and rerun onboarding. diff --git a/scripts/generate-platform-docs.py b/scripts/generate-platform-docs.py new file mode 100755 index 000000000..554e04671 --- /dev/null +++ b/scripts/generate-platform-docs.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Generate platform and provider tables from ci/platform-matrix.json. + +Reads the single-source-of-truth metadata and patches markdown tables +between sentinel comments in target files. + +Sentinel pairs: + / + / + +Usage: + python3 scripts/generate-platform-docs.py # patch files in place + python3 scripts/generate-platform-docs.py --check # exit 1 if out of sync +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +MATRIX_PATH = REPO_ROOT / "ci" / "platform-matrix.json" + +# Each entry: (sentinel_name, table_generator_key, list of target files) +TABLES = [ + ( + "platform-matrix", + "platforms", + [ + REPO_ROOT / "README.md", + REPO_ROOT / "docs" / "get-started" / "quickstart.md", + ], + ), + ( + "provider-status", + "providers", + [ + REPO_ROOT / "docs" / "inference" / "inference-options.md", + ], + ), +] + + +def _sentinel_re(name: str) -> re.Pattern: + return re.compile( + rf"()\n.*?\n()", + re.DOTALL, + ) + + +def load_matrix() -> dict: + with open(MATRIX_PATH) as f: + return json.load(f) + + +def generate_platform_table(platforms: list[dict]) -> str: + """Build a markdown table from platform entries. + + Deferred entries are tracked in the metadata but excluded from + user-facing tables — they have no validated setup path yet. + """ + STATUS_LABELS = { + "tested": "Tested", + "caveated": "Tested with limitations", + "experimental": "Experimental", + } + header = "| OS | Container runtime | Status | Notes |" + separator = "|----|-------------------|--------|-------|" + rows = [] + for p in platforms: + if p["status"] == "deferred": + continue + runtimes = ", ".join(p["runtimes"]) + status = STATUS_LABELS.get(p["status"], p["status"].capitalize()) + rows.append(f"| {p['name']} | {runtimes} | {status} | {p['notes']} |") + return "\n".join([header, separator, *rows]) + + +def generate_provider_table(providers: list[dict]) -> str: + """Build a markdown table from provider entries. + + Deferred entries are excluded from user-facing tables. + """ + header = "| Provider | Status | Endpoint type | Notes |" + separator = "|----------|--------|---------------|-------|" + rows = [] + for p in providers: + if p["status"] == "deferred": + continue + status = p["status"].capitalize() + rows.append(f"| {p['name']} | {status} | {p['endpoint_type']} | {p['notes']} |") + return "\n".join([header, separator, *rows]) + + +TABLE_GENERATORS = { + "platforms": generate_platform_table, + "providers": generate_provider_table, +} + + +def patch_file(path: Path, sentinel_name: str, table: str, check_only: bool) -> bool: + """Replace content between sentinels. Returns True if file was changed.""" + text = path.read_text() + begin = f"" + end = f"" + if begin not in text: + return False + + if end not in text: + raise ValueError( + f"{path.relative_to(REPO_ROOT)} has '{begin}' but no matching '{end}'" + ) + + pattern = _sentinel_re(sentinel_name) + replacement = f"\n{table}\n" + new_text = pattern.sub(replacement, text) + + if new_text == text: + return False + + if check_only: + print(f" DIFF {path.relative_to(REPO_ROOT)} [{sentinel_name}]") + return True + + path.write_text(new_text) + print(f" PATCH {path.relative_to(REPO_ROOT)} [{sentinel_name}]") + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--check", + action="store_true", + help="Check mode: exit 1 if any file is out of sync (no writes)", + ) + args = parser.parse_args() + + if not MATRIX_PATH.exists(): + print(f"Error: {MATRIX_PATH} not found", file=sys.stderr) + sys.exit(1) + + matrix = load_matrix() + + print(f"{'Checking' if args.check else 'Patching'} tables from {MATRIX_PATH.name}:") + diffs = [] + missing = [] + ok = [] + + for sentinel_name, data_key, target_files in TABLES: + generator = TABLE_GENERATORS[data_key] + table = generator(matrix[data_key]) + for path in target_files: + if not path.exists(): + print(f" MISS {path.relative_to(REPO_ROOT)}", file=sys.stderr) + missing.append(path) + continue + changed = patch_file(path, sentinel_name, table, check_only=args.check) + if changed: + diffs.append(path) + else: + ok.append(path) + + for path in ok: + print(f" OK {path.relative_to(REPO_ROOT)}") + + if missing: + print(f"\n{len(missing)} configured target file(s) missing.", file=sys.stderr) + sys.exit(1) + + if args.check and diffs: + print(f"\n{len(diffs)} file(s) out of sync. Run: python3 scripts/generate-platform-docs.py") + sys.exit(1) + + if not args.check and diffs: + print(f"\n{len(diffs)} file(s) patched.") + elif not diffs: + print("\nAll files in sync.") + + +if __name__ == "__main__": + main()