diff --git a/.github/workflows/build-sandboxes.yml b/.github/workflows/build-sandboxes.yml new file mode 100644 index 0000000..6768a04 --- /dev/null +++ b/.github/workflows/build-sandboxes.yml @@ -0,0 +1,219 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Sandbox Images + +on: + push: + branches: [main] + paths: + - "sandboxes/**" + pull_request: + branches: [main] + paths: + - "sandboxes/**" + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_PREFIX: ${{ github.repository }} + +permissions: + contents: read + packages: write + +jobs: + # --------------------------------------------------------------------------- + # Detect which sandbox images have changed + # --------------------------------------------------------------------------- + detect-changes: + name: Detect changed sandboxes + runs-on: ubuntu-latest + outputs: + base-changed: ${{ steps.changes.outputs.base_changed }} + sandboxes: ${{ steps.changes.outputs.sandboxes }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine changed sandboxes + id: changes + run: | + set -euo pipefail + + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + # Build everything on manual trigger + BASE_CHANGED="true" + SANDBOXES=$(find sandboxes -mindepth 1 -maxdepth 1 -type d -name '*.base' -prune -o -exec test -f {}/Dockerfile \; -print \ + | xargs -I{} basename {} \ + | grep -v '^base$' \ + | jq -R -s -c 'split("\n") | map(select(length > 0))') + else + if [ "${{ github.event_name }}" = "pull_request" ]; then + BASE_SHA="${{ github.event.pull_request.base.sha }}" + else + BASE_SHA="${{ github.event.before }}" + fi + + CHANGED=$(git diff --name-only "$BASE_SHA" "${{ github.sha }}" -- sandboxes/) + + # Check if base changed + if echo "$CHANGED" | grep -q '^sandboxes/base/'; then + BASE_CHANGED="true" + # When base changes, rebuild all sandboxes that have a Dockerfile + SANDBOXES=$(find sandboxes -mindepth 1 -maxdepth 1 -type d -exec test -f {}/Dockerfile \; -print \ + | xargs -I{} basename {} \ + | grep -v '^base$' \ + | jq -R -s -c 'split("\n") | map(select(length > 0))') + else + BASE_CHANGED="false" + SANDBOXES=$(echo "$CHANGED" \ + | cut -d'/' -f2 \ + | sort -u \ + | while read -r name; do + if [ "$name" != "base" ] && [ -f "sandboxes/${name}/Dockerfile" ]; then echo "$name"; fi + done \ + | jq -R -s -c 'split("\n") | map(select(length > 0))') + fi + fi + + echo "base_changed=${BASE_CHANGED}" >> "$GITHUB_OUTPUT" + echo "sandboxes=${SANDBOXES}" >> "$GITHUB_OUTPUT" + echo "Base changed: ${BASE_CHANGED}" + echo "Will build: ${SANDBOXES}" + + # --------------------------------------------------------------------------- + # Build the base sandbox image (other sandboxes depend on this) + # --------------------------------------------------------------------------- + build-base: + name: Build base + needs: detect-changes + if: needs.detect-changes.outputs.base-changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Lowercase image prefix + id: repo + run: echo "image_prefix=${IMAGE_PREFIX,,}" >> "$GITHUB_OUTPUT" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ steps.repo.outputs.image_prefix }}/sandboxes/base + tags: | + type=sha,prefix= + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: sandboxes/base + platforms: linux/amd64,linux/arm64 + push: ${{ github.ref == 'refs/heads/main' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=base + cache-to: type=gha,mode=max,scope=base + + # --------------------------------------------------------------------------- + # Build dependent sandbox images (after base completes) + # --------------------------------------------------------------------------- + build: + name: Build ${{ matrix.sandbox }} + needs: [detect-changes, build-base] + if: | + always() && + needs.detect-changes.result == 'success' && + (needs.build-base.result == 'success' || needs.build-base.result == 'skipped') && + needs.detect-changes.outputs.sandboxes != '[]' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sandbox: ${{ fromJson(needs.detect-changes.outputs.sandboxes) }} + steps: + - uses: actions/checkout@v4 + + - name: Lowercase image prefix + id: repo + run: echo "image_prefix=${IMAGE_PREFIX,,}" >> "$GITHUB_OUTPUT" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + # On PRs, use a local registry so the base image built in this job is + # accessible to the subsequent buildx build (docker-container driver + # cannot see images loaded into the host daemon). + - name: Start local registry (PR only) + if: github.ref != 'refs/heads/main' + run: docker run -d -p 5000:5000 --name registry registry:2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: ${{ github.ref != 'refs/heads/main' && 'network=host' || '' }} + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # On PRs the base image is not in GHCR. Build it locally, push to the + # local registry, and override BASE_IMAGE to point there. + - name: Build base image locally (PR only) + if: github.ref != 'refs/heads/main' + uses: docker/build-push-action@v6 + with: + context: sandboxes/base + push: true + tags: localhost:5000/sandboxes/base:latest + cache-from: type=gha,scope=base + + - name: Set BASE_IMAGE + id: base + run: | + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + echo "image=${{ env.REGISTRY }}/${{ steps.repo.outputs.image_prefix }}/sandboxes/base:latest" >> "$GITHUB_OUTPUT" + else + echo "image=localhost:5000/sandboxes/base:latest" >> "$GITHUB_OUTPUT" + fi + + - name: Generate image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ steps.repo.outputs.image_prefix }}/sandboxes/${{ matrix.sandbox }} + tags: | + type=sha,prefix= + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: sandboxes/${{ matrix.sandbox }} + platforms: ${{ github.ref == 'refs/heads/main' && 'linux/amd64,linux/arm64' || 'linux/amd64' }} + push: ${{ github.ref == 'refs/heads/main' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + BASE_IMAGE=${{ steps.base.outputs.image }} + cache-from: type=gha,scope=${{ matrix.sandbox }} + cache-to: type=gha,mode=max,scope=${{ matrix.sandbox }} diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml new file mode 100644 index 0000000..10679e9 --- /dev/null +++ b/.github/workflows/dco.yml @@ -0,0 +1,17 @@ +name: DCO + +on: + pull_request: + branches: [main] + +jobs: + dco: + name: DCO Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check DCO sign-off + uses: gsactions/dco-check@v1.1.1 diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml new file mode 100644 index 0000000..27856ad --- /dev/null +++ b/.github/workflows/license-check.yml @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: License Headers + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + license-headers: + name: Check License Headers + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Check SPDX license headers + run: python scripts/check_license_headers.py --check diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0937593 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,72 @@ +# Contributing to NemoClaw Community + +Thank you for your interest in contributing to the NemoClaw Community ecosystem. This guide covers everything you need to get started. + +## Ways to Contribute + +- **Sandbox images** -- Add new domain-specific sandbox environments under `sandboxes/` +- **Skills** -- Create agent skills and tool definitions inside a sandbox's `skills/` directory +- **Bug fixes** -- Fix issues in existing sandboxes, skills, or configurations +- **Documentation** -- Improve READMEs, guides, and usage examples +- **Integrations** -- Connect NemoClaw to new tools, platforms, or workflows + +## Getting Started + +1. Fork this repository +2. Clone your fork locally +3. Create a feature branch from `main` + +```bash +git clone https://github.com//NemoClaw-Community.git +cd NemoClaw-Community +git checkout -b my-feature +``` + +## Adding a Sandbox Image + +Each sandbox lives in its own directory under `sandboxes/`: + +``` +sandboxes/my-sandbox/ + Dockerfile + README.md + ... +``` + +Requirements: +- A `Dockerfile` that builds cleanly +- A `README.md` describing the sandbox's purpose, usage, and any prerequisites +- Keep images minimal -- only include what's needed for the workload + +## Adding a Skill + +Skills live inside their sandbox's `skills/` directory (e.g., `sandboxes/openclaw/skills/my-skill/`). Each skill should include: +- A `SKILL.md` describing what it does and when to use it +- Any supporting files the skill needs +- A README with usage examples + +## Submitting a Pull Request + +1. Ensure your changes are focused -- one feature or fix per PR +2. Include a clear description of what your PR does and why +3. Test your changes locally before submitting +4. Update any relevant documentation + +## Development Guidelines + +- Follow existing naming conventions and directory structures +- Write clear commit messages +- Keep PRs small and reviewable +- Respond to review feedback promptly + +## Reporting Issues + +Use GitHub Issues for bug reports and feature requests. Include: +- A clear title and description +- Steps to reproduce (for bugs) +- Expected vs. actual behavior +- Environment details (OS, Docker version, GPU, etc.) + +## License + +By contributing, you agree that your contributions will be licensed under the Apache 2.0 License. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a3f705 --- /dev/null +++ b/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2025 NVIDIA Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 56b69ec..58a37fd 100644 --- a/README.md +++ b/README.md @@ -1,154 +1,53 @@ -# __NVIDIA_OSS__ Standard Repo Template - -This README file is from the NVIDIA_OSS standard repo template of [PLC-OSS-Template](https://github.com/NVIDIA-GitHub-Management/PLC-OSS-Template?tab=readme-ov-file). It provides a list of files in the PLC-OSS-Template and guidelines on how to use (clone and customize) them. - -**Upon completing the customization for the project repo, the repo admin should replace this README template with the project specific README file.** - -- Files (org-wide templates in the NVIDIA .github org repo; per-repo overrides allowed) in [PLC-OSS-Template](https://github.com/NVIDIA-GitHub-Management/PLC-OSS-Template?tab=readme-ov-file) - - - Root - - README.md skeleton (CTA + Quickstart + Support/Security/Governance links) - - LICENSE (Apache 2.0 by default) - - For other licenses, see the [Confluence page](https://confluence.nvidia.com/pages/viewpage.action?pageId=788418816) for other licenses - - CLA.md file (delete if not using MIT or BSD licenses) - - CODE_OF_CONDUCT.md - - SECURITY.md (vuln reporting path) - - CONTRIBUTING.md (base; repo can add specifics) - - SUPPORT.md (Support levels/channels) - - GOVERNANCE.md (baseline; repo may extend) - - CITATION.md (for projects that need citation) - - - .github/ - - ISSUE_TEMPLATE/ () - - bug.yml, feature.yml, task.yml, config.yml - - PULL_REQUEST_TEMPLATE.md () - - workflows/ - - Note: workflow-templates/ for starter workflows should live in the org-level .github repo, not per-repo - - - Repo-specific (not org-template, maintained by the team) - - CODEOWNERS (place at .github/CODEOWNERS or repo root) - - CHANGELOG.md (or RELEASE.md) - - ROADMAP.md - - MAINTAINERS.md - - NOTICE or THIRD_PARTY_NOTICES / THIRD_PARTY_LICENSES (dependency specific) - - Build/package files (CMake, pyproject, Dockerfile, etc.) - - - Recommended structure and hygiene - - docs/ - - examples/ - - tests/ - - scripts/ - - Container/dev env: Dockerfile, docker/, .devcontainer/ (optional) - - Build/package (language-specific): - - Python: pyproject.toml, setup.cfg/setup.py, requirements.txt, environment.yml - - C++: CMakeLists.txt, cmake/, vcpkg.json - - Repo hygiene: .gitignore, .gitattributes, .editorconfig, .pre-commit-config.yaml, .clang-format - - -## Usage of [PLC-OSS-Template](https://github.com/NVIDIA-GitHub-Management/PLC-OSS-Template?tab=readme-ov-file) for NEW NVIDIA OSS repos - -1. Clone the [PLC-OSS-Template](https://github.com/NVIDIA-GitHub-Management/PLC-OSS-Template?tab=readme-ov-file) -2. Find/replace all in the clone of `___PROJECT___` and `__PROJECT_NAME__` with the name of the specific project. -3. Inspect all files to make sure all replacements work and update text as needed - - -**What you can reuse immediately** -- CODE_OF_CONDUCT.md -- SECURITY.md -- CONTRIBUTING.md (base) -- .github/ISSUE_TEMPLATE/.yml (bug/feature/task + config.yml) -- .github/PULL_REQUEST_TEMPLATE.md -- Reusable workflows - -**What you must customize per repo** -- README.md: copy the skeleton and fill in product-specific details (Quickstart, Requirements, Usage, Support level, links) -- LICENSE: check file is correct, update year, consult Confluence for alternatives https://confluence.nvidia.com/pages/viewpage.action?pageId=788418816, add CLA.md only if your license/process requires it -- CODEOWNERS: replace with your GitHub team handle(s). Place at .github/CODEOWNERS (or repo root) -- MAINTAINERS.md: list maintainers names/roles, escalation path -- CHANGELOG.md (or RELEASE.md): track releases/changes -- SUPPORT.md: Update for your project -- ROADMAP.md (optional): upcoming milestones -- NOTICE / THIRD_PARTY_NOTICES (if you ship third‑party content) -- Build/package files (CMake/pyproject/Dockerfile/etc.), tests/, docs/, examples/, scripts/ as appropriate -- Workflows: Edit if you need custom behavior - - -4. Change git origin to point to new repo and push -5. Remove the line break below and everything above it - -## Usage for existing NVIDIA OSS repos - -1. Follow the steps above, but add the files to your existing repo and merge - - ------------------------------------------ -# [Project Title] -One-sentence value proposition for users. Who is it for, and why it matters. - -# Overview -What the project does? Why the project is useful? -Provide a brief overview, highlighting key features or problem-solving capabilities. - -# Getting Started -Guide users on how they can get started with the project. This should include basic installation step, quick-start examples -```bash -# Option A: Package manager (pip/conda/npm/etc.) - +# NemoClaw Community -# Option B: Container -docker run +[NemoClaw](https://github.com/NVIDIA/NemoClaw) is the runtime environment for autonomous agents -- the infrastructure where they live, work, and verify. It provides a programmable factory where agents can spin up physics simulations to master tasks, generate synthetic data to fix edge cases, and safely iterate through thousands of failures in isolated sandboxes. The core engine includes the sandbox runtime, policy engine, gateway (with k3s harness), privacy router, and CLI. -# Verify (hello world) - -``` -# Requirements -Include a list of pre-requisites. -- OS/Arch: -- Runtime/Compiler: -- GPU/Drivers (if applicable): CUDA , driver , etc. +This repo is the community ecosystem around NemoClaw -- a hub for contributed skills, sandbox images, launchables, and integrations that extend its capabilities. For the core engine, docs, and published artifacts (PyPI, containers, binaries), see the [NemoClaw](https://github.com/NVIDIA/NemoClaw) repo. + +## What's Here + +| Directory | Description | +| ------------ | --------------------------------------------------------------------------------- | +| `brev/` | [Brev](https://brev.dev) launchable for one-click cloud deployment of NemoClaw | +| `sandboxes/` | Pre-built sandbox images for domain-specific workloads (each with its own skills) | + +### Sandboxes + +| Sandbox | Description | +| ----------------------- | ------------------------------------------------------------ | +| `sandboxes/base/` | Foundational image with system tools, users, and dev environment | +| `sandboxes/sdg/` | Synthetic data generation workflows | +| `sandboxes/openclaw/` | OpenClaw -- open agent manipulation and control | +| `sandboxes/simulation/` | General-purpose simulation sandboxes | + +## Getting Started + +### Prerequisites + +- [NemoClaw CLI](https://github.com/NVIDIA/NemoClaw) installed (`uv pip install nemoclaw`) +- Docker or a compatible container runtime +- NVIDIA GPU with appropriate drivers (for GPU-accelerated images) + +### Quick Start with Brev + +TODO: Add Brev instructions + +### Using Sandboxes -# Usage -```bash -# Minimal runnable snippet (≤20 lines) - -``` -- More examples/tutorials: -- API reference: - -# Performance (Optional) -Summary of benchmarks; link to detailed results and hardware used. - -## Releases & Roadmap -- Releases/Changelog: -- (Optional) Next milestones or link to `ROADMAP.md`. - -# Contribution Guidelines -- Start here: `CONTRIBUTING.md` -- Code of Conduct: `CODE_OF_CONDUCT.md` -- Development quickstart (build/test): ```bash - && && +nemoclaw sandbox create --from openclaw ``` -## Governance & Maintainers -- Governance: `GOVERNANCE.md` -- Maintainers: -- Labeling/triage policy: -## Security -- Vulnerability disclosure: `SECURITY.md` -- Do not file public issues for security reports. +The `--from` flag accepts any sandbox defined under `sandboxes/` (e.g., `openclaw`, `sdg`, `simulation`), a local path, or a container image reference. + +## Contributing -## Support -- Level: -- How to get help: Issues/Discussions/ -- Response expectations (if any). +See [CONTRIBUTING.md](CONTRIBUTING.md). + +## Security -# Community -Provide the channel for community communications. +See [SECURITY.md](SECURITY.md). Do not file public issues for security vulnerabilities. -# References -Provide a list of related references +## License -# License -This project is licensed under the [NAME HERE] License - see the LICENSE.md file for details -- License: +This project is licensed under the Apache 2.0 License -- see the [LICENSE](LICENSE) file for details. diff --git a/brev/.gitkeep b/brev/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sandboxes/base/Dockerfile b/sandboxes/base/Dockerfile new file mode 100644 index 0000000..cd2b8ee --- /dev/null +++ b/sandboxes/base/Dockerfile @@ -0,0 +1,82 @@ +# syntax=docker/dockerfile:1.4 + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# NemoClaw Community base sandbox image +# +# Provides the foundational system tools, users, and developer environment +# that all other community sandbox images build on top of. +# +# Build: docker build -t nemoclaw-base . + +# System base +FROM ubuntu:24.04 AS system + +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /sandbox + +# Core system dependencies +# python3 + pip: agent scripting and SDK usage +# iproute2: network namespace management (ip netns, veth pairs) +# dnsutils: dig, nslookup +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + dnsutils \ + iproute2 \ + iputils-ping \ + net-tools \ + netcat-openbsd \ + python3 \ + python3-pip \ + python3-venv \ + traceroute \ + && rm -rf /var/lib/apt/lists/* + +# Create supervisor and sandbox users/groups +RUN groupadd -r supervisor && useradd -r -g supervisor -s /usr/sbin/nologin supervisor && \ + groupadd -r sandbox && useradd -r -g sandbox -d /sandbox -s /bin/bash sandbox + +# Developer tools +FROM system AS devtools + +# Node.js 22 + build toolchain (needed by npm packages with native addons) +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get install -y --no-install-recommends \ + build-essential \ + git \ + nodejs \ + vim-tiny \ + nano \ + && rm -rf /var/lib/apt/lists/* + +# GitHub CLI +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + -o /usr/share/keyrings/githubcli-archive-keyring.gpg && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + > /etc/apt/sources.list.d/github-cli.list && \ + apt-get update && apt-get install -y --no-install-recommends gh && \ + rm -rf /var/lib/apt/lists/* + +# uv (Python package manager) +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +# Final base image +FROM devtools AS final + +# Ensure policy directory exists +RUN mkdir -p /etc/navigator + +# Set up sandbox user home directory +RUN mkdir -p /sandbox/.agents/skills && \ + printf 'export PS1="\\u@\\h:\\w\\$ "\n' > /sandbox/.bashrc && \ + printf '[ -f ~/.bashrc ] && . ~/.bashrc\n' > /sandbox/.profile && \ + chown -R sandbox:sandbox /sandbox + +USER sandbox + +ENTRYPOINT ["/bin/bash"] diff --git a/sandboxes/base/README.md b/sandboxes/base/README.md new file mode 100644 index 0000000..a8d5f46 --- /dev/null +++ b/sandboxes/base/README.md @@ -0,0 +1,46 @@ +# Base Sandbox + +The foundational sandbox image that all other NemoClaw Community sandbox images build from. + +## What's Included + +| Category | Tools | +|----------|-------| +| OS | Ubuntu 24.04 | +| Language | `python3`, `node` (22) | +| Developer | `gh`, `git`, `vim`, `nano`, `uv` | +| Networking | `ping`, `dig`, `nslookup`, `nc`, `traceroute`, `netstat`, `curl` | + +### Users + +| User | Purpose | +|------|---------| +| `supervisor` | Privileged process management (nologin shell) | +| `sandbox` | Unprivileged user for agent workloads (default) | + +### Directory Layout + +``` +/sandbox/ # Home directory (sandbox user) + .bashrc, .profile # Shell init + .agents/skills/ # Agent skill discovery +``` + +## Build + +```bash +docker build -t nemoclaw-base . +``` + +## Building a Sandbox on Top + +Other sandbox images should use this as their base: + +```dockerfile +ARG BASE_IMAGE=ghcr.io/nvidia/nemoclaw-community/sandboxes/base:latest +FROM ${BASE_IMAGE} + +# Add your sandbox-specific layers here +``` + +See `sandboxes/openclaw/` for an example. diff --git a/sandboxes/openclaw/Dockerfile b/sandboxes/openclaw/Dockerfile new file mode 100644 index 0000000..5a2f31d --- /dev/null +++ b/sandboxes/openclaw/Dockerfile @@ -0,0 +1,33 @@ +# syntax=docker/dockerfile:1.4 + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# OpenClaw sandbox image for NemoClaw +# +# Builds on the community base sandbox and adds OpenClaw. +# Build: docker build -t nemoclaw-openclaw --build-arg BASE_IMAGE=nemoclaw-base . +# Run: nemoclaw sandbox create --from openclaw + +ARG BASE_IMAGE=ghcr.io/nvidia/nemoclaw-community/sandboxes/base:latest +FROM ${BASE_IMAGE} + +USER root + +# Install OpenClaw CLI +RUN npm install -g openclaw + +# Copy sandbox policy +COPY policy.yaml /etc/navigator/policy.yaml + +# Copy the OpenClaw startup helper +COPY openclaw-start.sh /usr/local/bin/openclaw-start +RUN chmod +x /usr/local/bin/openclaw-start + +# Create OpenClaw workspace +RUN mkdir -p /sandbox/.openclaw && \ + chown -R sandbox:sandbox /sandbox/.openclaw + +USER sandbox + +ENTRYPOINT ["/bin/bash"] diff --git a/sandboxes/openclaw/README.md b/sandboxes/openclaw/README.md new file mode 100644 index 0000000..dcd263a --- /dev/null +++ b/sandboxes/openclaw/README.md @@ -0,0 +1,57 @@ +# OpenClaw Sandbox + +NemoClaw sandbox image pre-configured with [OpenClaw](https://github.com/openclaw) for open agent manipulation and control. + +## What's Included + +- **OpenClaw CLI** -- Agent orchestration and gateway management +- **OpenClaw Gateway** -- Local gateway for agent-to-tool communication +- **Node.js 22** -- Runtime required by the OpenClaw gateway +- **openclaw-start** -- Helper script that onboards and starts the gateway automatically + +## Build + +```bash +docker build -t nemoclaw-openclaw . +``` + +To build against a specific base image: + +```bash +docker build -t nemoclaw-openclaw --build-arg BASE_IMAGE=nemoclaw/sandbox:v0.1.0 . +``` + +## Usage + +### Create a sandbox + +```bash +nemoclaw sandbox create --from openclaw +``` + +### With port forwarding (to access the OpenClaw UI) + +```bash +nemoclaw sandbox create --from openclaw --forward 18789 -- openclaw-start +``` + +This runs the `openclaw-start` helper which: + +1. Runs `openclaw onboard` to configure the environment +2. Starts the OpenClaw gateway in the background +3. Prints the gateway URL (with auth token if available) + +Access the UI at `http://127.0.0.1:18789/`. + +### Manual startup + +If you prefer to start OpenClaw manually inside the sandbox: + +```bash +openclaw onboard +openclaw gateway run +``` + +## Configuration + +OpenClaw stores its configuration in `~/.openclaw/openclaw.json` inside the sandbox. The config is generated during `openclaw onboard`. diff --git a/sandboxes/openclaw/openclaw-start.sh b/sandboxes/openclaw/openclaw-start.sh new file mode 100644 index 0000000..3c4a92b --- /dev/null +++ b/sandboxes/openclaw/openclaw-start.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# openclaw-start — Configure OpenClaw and start the gateway. +# Designed for NemoClaw sandboxes. +# +# Usage: +# nemoclaw sandbox create --from openclaw --forward 18789 -- openclaw-start +set -euo pipefail + +openclaw onboard + +nohup openclaw gateway run > /tmp/gateway.log 2>&1 & + +CONFIG_FILE="${HOME}/.openclaw/openclaw.json" +token=$(grep -o '"token"\s*:\s*"[^"]*"' "${CONFIG_FILE}" 2>/dev/null | head -1 | cut -d'"' -f4 || true) + +echo "" +echo "OpenClaw gateway starting in background." +echo " Logs: /tmp/gateway.log" +if [ -n "${token}" ]; then + echo " UI: http://127.0.0.1:18789/?token=${token}" +else + echo " UI: http://127.0.0.1:18789/" +fi +echo "" diff --git a/sandboxes/openclaw/policy.yaml b/sandboxes/openclaw/policy.yaml new file mode 100644 index 0000000..92d9a41 --- /dev/null +++ b/sandboxes/openclaw/policy.yaml @@ -0,0 +1,167 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +version: 1 + +# --- Sandbox setup configuration (queried once at startup) --- + +filesystem_policy: + include_workdir: true + read_only: + - /usr + - /lib + - /proc + - /dev/urandom + - /app + - /etc + - /var/log + read_write: + - /sandbox + - /tmp + - /dev/null + +landlock: + compatibility: best_effort + +process: + run_as_user: sandbox + run_as_group: sandbox + +# --- Network policies (queried per-CONNECT request) --- +# +# Each named policy maps a set of allowed (binary, endpoint) pairs. +# Binary identity is resolved via /proc/net/tcp inode lookup + /proc/{pid}/exe. +# Ancestors (/proc/{pid}/status PPid walk) and cmdline paths are also matched. +# SHA256 integrity is enforced in Rust via trust-on-first-use, not here. + +network_policies: + claude_code: + name: claude_code + endpoints: + - { host: api.anthropic.com, port: 443, protocol: rest, enforcement: enforce, access: full, tls: terminate } + - { host: statsig.anthropic.com, port: 443 } + - { host: sentry.io, port: 443 } + - { host: raw.githubusercontent.com, port: 443 } + - { host: platform.claude.com, port: 443 } + binaries: + - { path: /usr/local/bin/claude } + - { path: /usr/bin/node } + gitlab: + name: gitlab + endpoints: + - { host: gitlab.com, port: 443 } + - { host: gitlab.mycorp.com, port: 443 } + binaries: + - { path: /usr/bin/glab } + github: + name: github + endpoints: + - host: github.com + port: 443 + protocol: rest + tls: terminate + enforcement: enforce + rules: + # Git Smart HTTP read-only: allow clone, fetch, pull + # Discovery (query string is included in path matching) + - allow: + method: GET + path: "/**/info/refs*" + # Data transfer for reads (all repos) + - allow: + method: POST + path: "/**/git-upload-pack" + # Data transfer for writes (alpha-claw only) + - allow: + method: POST + path: "/johntmyers/alpha-claw*/git-receive-pack" + binaries: + - { path: /usr/bin/git } + nvidia: + name: nvidia + endpoints: + - { host: integrate.api.nvidia.com, port: 443 } + binaries: + - { path: /usr/bin/curl } + - { path: /bin/bash } + - { path: /usr/local/bin/opencode } + nvidia_web: + name: nvidia_web + endpoints: + - { host: nvidia.com, port: 443 } + - { host: www.nvidia.com, port: 443 } + binaries: + - { path: /usr/bin/curl } + + # --- GitHub repo-scoped policy (L7, TLS-terminated) --- + # Combined policy for api.github.com to avoid OPA complete-rule conflict. + # - johntmyers/alpha-claw: full access (all methods including DELETE) + # - johntmyers/bravo-claw: read-only + create/edit issues + github_repos: + name: github_repos + endpoints: + - host: api.github.com + port: 443 + protocol: rest + tls: terminate + enforcement: enforce + rules: + # Read-only access to all GitHub API paths + - allow: + method: GET + path: "/**" + - allow: + method: HEAD + path: "/**" + - allow: + method: OPTIONS + path: "/**" + # GraphQL API (used by gh CLI for most operations) + # - allow: + # method: POST + # path: "/graphql" + # alpha-claw: full write access + - allow: + method: "*" + path: "/repos/johntmyers/alpha-claw/**" + # bravo-claw: create + edit issues + - allow: + method: POST + path: "/repos/johntmyers/bravo-claw/issues" + - allow: + method: PATCH + path: "/repos/johntmyers/bravo-claw/issues/*" + binaries: + - { path: /usr/local/bin/claude } + - { path: /usr/bin/gh } + + vscode: + name: vscode + endpoints: + - { host: update.code.visualstudio.com, port: 443 } + - { host: "*.vo.msecnd.net", port: 443 } + - { host: vscode.download.prss.microsoft.com, port: 443 } + - { host: marketplace.visualstudio.com, port: 443 } + - { host: "*.gallerycdn.vsassets.io", port: 443 } + binaries: + - { path: /usr/bin/curl } + - { path: /usr/bin/wget } + - { path: "/sandbox/.vscode-server/**" } + - { path: "/sandbox/.vscode-remote-containers/**" } + + # --- Private network access (allowed_ips demo) --- + # Allows any binary to reach services on the k3s cluster pod network + # (10.42.0.0/16). Without allowed_ips, the proxy's SSRF check blocks + # all connections to private RFC 1918 addresses. + cluster_pods: + name: cluster_pods + endpoints: + - port: 8080 + allowed_ips: + - "10.42.0.0/16" + binaries: + - { path: "/**" } + +inference: + allowed_routes: + - local diff --git a/sandboxes/openclaw/skills/.gitkeep b/sandboxes/openclaw/skills/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sandboxes/sdg/.gitkeep b/sandboxes/sdg/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sandboxes/simulation/.gitkeep b/sandboxes/simulation/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/check_license_headers.py b/scripts/check_license_headers.py new file mode 100644 index 0000000..7ce2333 --- /dev/null +++ b/scripts/check_license_headers.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Check or add SPDX license headers on source files. + +Usage: + # Check mode (CI) — exit 1 if any file is missing a header + python scripts/check_license_headers.py --check + + # Add/update headers on all source files + python scripts/check_license_headers.py + + # Operate on specific files only + python scripts/check_license_headers.py path/to/file.py +""" + +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +COPYRIGHT_TEXT = ( + "Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved." +) +LICENSE_ID = "Apache-2.0" + +# Map file extensions to their line-comment prefix. +COMMENT_STYLES: dict[str, str] = { + ".py": "#", + ".sh": "#", + ".yaml": "#", + ".yml": "#", + ".toml": "#", +} + +# Directories to skip entirely (relative to repo root). +EXCLUDE_DIRS: set[str] = { + ".git", + ".github", + ".venv", + "__pycache__", +} + +# Individual filenames to skip. +EXCLUDE_FILES: set[str] = { + ".gitkeep", +} + +# --------------------------------------------------------------------------- +# Header generation +# --------------------------------------------------------------------------- + + +def make_header(comment: str) -> str: + """Return the two-line SPDX header for a given comment prefix.""" + return ( + f"{comment} SPDX-FileCopyrightText: {COPYRIGHT_TEXT}\n" + f"{comment} SPDX-License-Identifier: {LICENSE_ID}\n" + ) + + +# --------------------------------------------------------------------------- +# File discovery +# --------------------------------------------------------------------------- + + +def find_repo_root() -> Path: + """Walk up from CWD to find the directory containing .git.""" + path = Path.cwd() + while path != path.parent: + if (path / ".git").exists(): + return path + path = path.parent + return Path.cwd() + + +def is_excluded(rel: Path) -> bool: + """Return True if a path should be skipped.""" + rel_str = str(rel) + + if rel.name in EXCLUDE_FILES: + return True + + for exc_dir in EXCLUDE_DIRS: + if rel_str == exc_dir or rel_str.startswith(exc_dir + "/"): + return True + + return False + + +def is_dockerfile(path: Path) -> bool: + """Return True for Dockerfile variants (matched by name, not extension).""" + return path.name == "Dockerfile" or path.name.startswith("Dockerfile.") + + +def get_comment_style(path: Path) -> str | None: + """Return the comment prefix for a file, or None if unsupported.""" + if is_dockerfile(path): + return "#" + return COMMENT_STYLES.get(path.suffix) + + +def discover_files(root: Path) -> list[Path]: + """Walk the repo and return all files that should have headers.""" + results = [] + for dirpath, dirnames, filenames in os.walk(root): + rel_dir = Path(dirpath).relative_to(root) + + dirnames[:] = [d for d in dirnames if not is_excluded(rel_dir / d)] + + for fname in filenames: + fpath = Path(dirpath) / fname + rel = fpath.relative_to(root) + if is_excluded(rel): + continue + if get_comment_style(rel) is not None: + results.append(fpath) + + return sorted(results) + + +# --------------------------------------------------------------------------- +# Header checking and insertion +# --------------------------------------------------------------------------- + +SPDX_MARKER = "SPDX-License-Identifier" + + +def has_header(lines: list[str]) -> bool: + """Check if the SPDX header is present in the first 10 lines.""" + for line in lines[:10]: + if SPDX_MARKER in line: + return True + return False + + +def find_insertion_point(lines: list[str], path: Path) -> int: + """Determine where to insert the header.""" + if not lines: + return 0 + + first = lines[0] + + if first.startswith("#!"): + return 1 + + if is_dockerfile(path) and first.lower().startswith("# syntax="): + return 1 + + return 0 + + +def insert_header(content: str, comment: str, path: Path) -> str: + """Insert the SPDX header into file content, returning the new content.""" + header = make_header(comment) + lines = content.splitlines(keepends=True) + insert_at = find_insertion_point(lines, path) + + if insert_at == 0: + if lines: + return header + "\n" + content + return header + else: + before = lines[:insert_at] + after = lines[insert_at:] + return "".join(before) + "\n" + header + "\n" + "".join(after) + + +# --------------------------------------------------------------------------- +# Main logic +# --------------------------------------------------------------------------- + + +def process_file(path: Path, root: Path, *, check: bool, verbose: bool) -> bool: + """Process a single file. Returns True if the file is compliant.""" + rel = path.relative_to(root) + comment = get_comment_style(rel) + if comment is None: + return True + + content = path.read_text(encoding="utf-8") + lines = content.splitlines() + + if has_header(lines): + if verbose: + print(f" ok: {rel}") + return True + + if check: + print(f" MISSING: {rel}") + return False + + new_content = insert_header(content, comment, rel) + path.write_text(new_content, encoding="utf-8") + if verbose: + print(f" added: {rel}") + return True + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Check or add SPDX license headers on source files.", + ) + parser.add_argument( + "--check", + action="store_true", + help="Check mode: exit 1 if any file is missing a header.", + ) + parser.add_argument( + "--verbose", + "-v", + action="store_true", + help="Print status for every file processed.", + ) + parser.add_argument( + "paths", + nargs="*", + type=Path, + help="Specific files to process (default: all files under repo root).", + ) + args = parser.parse_args() + + root = find_repo_root() + + if args.paths: + files = [] + for p in args.paths: + p = p.resolve() + if not p.is_file(): + continue + rel = p.relative_to(root) + if is_excluded(rel): + continue + if get_comment_style(rel) is not None: + files.append(p) + else: + files = discover_files(root) + + if args.check: + print(f"Checking {len(files)} files for SPDX headers...") + else: + print(f"Processing {len(files)} files...") + + missing = [] + for f in files: + if not process_file(f, root, check=args.check, verbose=args.verbose): + missing.append(f) + + if args.check: + if missing: + print(f"\n{len(missing)} file(s) missing SPDX headers.") + return 1 + print("All files have SPDX headers.") + return 0 + + print("Done.") + return 0 + + +if __name__ == "__main__": + sys.exit(main())