v0.1 step 1: Dockerfile + per-language toolchain smoke

Monolith image with every toolchain in the spec:
- Python 3.12 + uv/ruff/mypy/pytest/pip-audit/semgrep
- Node 22 LTS + bun
- Go 1.22 + govulncheck/staticcheck
- Rust stable + cargo-audit/cargo-deny
- Ruby 3.x + bundler-audit
- PHP 8.x + composer/phpstan
- JDK 17 + 21 + Maven + Gradle
- .NET 8 SDK
- Swift 5.9.2
- Kotlin 1.9.25
- clang + cmake + valgrind + ASan/UBSan/TSan
- bash + shellcheck

smoke.sh proves each toolchain compiles + runs a hello-world.
compose.yml uses the existing 'sulkta' bridge network.

No API yet (steps 2-3); no MCP yet (step 7); no runner yet (step 4).
This is the foundation.

NOTE: docker build + smoke verification not yet run — sandbox doesn't
have docker. Needs `docker compose build && docker compose up` on Lucy
or any real Docker host before we trust the Dockerfile.

Spec: memory/spec-crafting-table.md
This commit is contained in:
Kayos 2026-04-29 07:29:53 -07:00
parent 5bd1b1de7e
commit 4e668a79e1
5 changed files with 619 additions and 1 deletions

48
.gitignore vendored Normal file
View file

@ -0,0 +1,48 @@
# Python
__pycache__/
*.pyc
*.pyo
.venv/
venv/
.mypy_cache/
.ruff_cache/
.pytest_cache/
# Node / TS
node_modules/
dist/
*.tsbuildinfo
# Rust
target/
# Go
/bin/
# Java / Kotlin / Gradle / Maven
build/
out/
.gradle/
*.class
*.jar
.mvn/
# .NET
.dotnet/
obj/
# Swift
.swiftpm/
.build/
*.xcodeproj/
# Misc
.cache/
.env
*.log
.DS_Store
# Editor
.vscode/
.idea/
*.swp

263
Dockerfile Normal file
View file

@ -0,0 +1,263 @@
# crafting-table v0.1 — polyglot dev/build/audit container
#
# Step 1 of 10: monolith image with every toolchain in the spec.
# Spec: Sulkta-Coop/openclaw-workspace/memory/spec-crafting-table.md
#
# Toolchain version pins (bump these to upgrade):
# NODE_VERSION 22.11.0 (LTS)
# GO_VERSION 1.22.10
# RUST_CHANNEL stable
# BUN_VERSION latest (rolling — pinned at install only)
# DOTNET_VERSION 8.0
# SWIFT_VERSION 5.9.2
# KOTLIN_VERSION 1.9.25
# GRADLE_VERSION 8.10
# JDK 17 (default) + 21 (alongside, via JAVA_HOME_21)
#
# Image runs as non-root user `crafter` (uid 1000) with passwordless sudo.
# Persistent caches mounted at /caches/{cargo,maven,gradle,npm,pip}.
# Workspace at /workspace. Per-job state at /data.
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8
# ---------- Toolchain version pins ----------
ENV NODE_VERSION=22.11.0 \
GO_VERSION=1.22.10 \
DOTNET_CHANNEL=8.0 \
SWIFT_VERSION=5.9.2 \
SWIFT_PLATFORM=ubuntu22.04 \
KOTLIN_VERSION=1.9.25 \
GRADLE_VERSION=8.10
# ============================================================
# 1. System base — apt packages
# ============================================================
RUN apt-get update && apt-get install -y --no-install-recommends \
curl wget git ca-certificates gnupg lsb-release apt-transport-https \
build-essential pkg-config make cmake ninja-build \
jq ripgrep fd-find \
valgrind clang lld llvm \
python3 python3-pip python3-venv python3-dev \
php-cli php-curl php-mbstring php-xml php-zip composer \
ruby ruby-dev ruby-bundler \
bash shellcheck bats \
openjdk-17-jdk-headless \
sudo unzip xz-utils zstd \
libcurl4 libxml2 libedit2 libsqlite3-0 libpython3-dev \
libncurses6 libtinfo6 libgcc-s1 libstdc++6 \
libssl-dev libffi-dev \
zlib1g-dev liblzma-dev libbz2-dev \
locales tzdata \
&& ln -sf /usr/bin/fdfind /usr/local/bin/fd \
&& rm -rf /var/lib/apt/lists/*
# ============================================================
# 2. Node 22 LTS via NodeSource
# ============================================================
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& npm install -g pnpm tsx eslint typescript
# ============================================================
# 3. Go (download from go.dev tarball)
# ============================================================
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) GOARCH=amd64 ;; \
arm64) GOARCH=arm64 ;; \
*) echo "Unsupported arch $ARCH" && exit 1 ;; \
esac \
&& curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz" -o /tmp/go.tgz \
&& tar -C /usr/local -xzf /tmp/go.tgz \
&& rm /tmp/go.tgz
ENV PATH=/usr/local/go/bin:/root/go/bin:$PATH \
GOPATH=/root/go
# ============================================================
# 4. Microsoft .NET 8 SDK (Microsoft apt repo for bookworm/12)
# ============================================================
RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/microsoft.gpg] https://packages.microsoft.com/debian/12/prod bookworm main" > /etc/apt/sources.list.d/microsoft.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends dotnet-sdk-8.0 \
&& rm -rf /var/lib/apt/lists/*
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
DOTNET_NOLOGO=1
# ============================================================
# 5. Swift (Ubuntu 22.04 tarball — works on Debian bookworm
# because bookworm ships the right libicu/libstdc++ baseline)
# ============================================================
RUN ARCH="$(dpkg --print-architecture)" \
&& SWIFT_PLAT="${SWIFT_PLATFORM}" \
&& case "$ARCH" in \
amd64) SWIFT_TARBALL_NAME="swift-${SWIFT_VERSION}-RELEASE-${SWIFT_PLAT}" ;; \
arm64) SWIFT_TARBALL_NAME="swift-${SWIFT_VERSION}-RELEASE-${SWIFT_PLAT}-aarch64" ;; \
*) echo "Unsupported arch $ARCH for Swift" && exit 1 ;; \
esac \
&& apt-get update \
&& apt-get install -y --no-install-recommends libpython3-dev libxml2-dev \
libcurl4-openssl-dev libedit-dev libsqlite3-dev libtinfo-dev libncurses-dev \
&& rm -rf /var/lib/apt/lists/* \
&& curl -fsSL "https://download.swift.org/swift-${SWIFT_VERSION}-release/${SWIFT_PLAT//./}/swift-${SWIFT_VERSION}-RELEASE/${SWIFT_TARBALL_NAME}.tar.gz" -o /tmp/swift.tgz \
&& mkdir -p /opt/swift \
&& tar -xzf /tmp/swift.tgz -C /opt/swift --strip-components=1 \
&& rm /tmp/swift.tgz
ENV PATH=/opt/swift/usr/bin:$PATH
# ============================================================
# 6. Kotlin compiler (direct download from GitHub release)
# ============================================================
RUN curl -fsSL "https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip" -o /tmp/kotlin.zip \
&& unzip -q /tmp/kotlin.zip -d /opt \
&& mv /opt/kotlinc /opt/kotlin \
&& rm /tmp/kotlin.zip
ENV PATH=/opt/kotlin/bin:$PATH
# ============================================================
# 7. JDK 21 alongside JDK 17 (Eclipse Temurin via apt)
# ============================================================
RUN mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb bookworm main" > /etc/apt/sources.list.d/adoptium.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends temurin-21-jdk \
&& rm -rf /var/lib/apt/lists/*
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \
JAVA_HOME_17=/usr/lib/jvm/java-17-openjdk-amd64 \
JAVA_HOME_21=/usr/lib/jvm/temurin-21-jdk-amd64
RUN update-alternatives --set java ${JAVA_HOME_17}/bin/java || true \
&& update-alternatives --set javac ${JAVA_HOME_17}/bin/javac || true
# ============================================================
# 8. Maven (apt) + Gradle (direct download — apt's gradle is ancient)
# ============================================================
RUN apt-get update \
&& apt-get install -y --no-install-recommends maven \
&& rm -rf /var/lib/apt/lists/* \
&& curl -fsSL "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" -o /tmp/gradle.zip \
&& unzip -q /tmp/gradle.zip -d /opt \
&& mv /opt/gradle-${GRADLE_VERSION} /opt/gradle \
&& rm /tmp/gradle.zip
ENV PATH=/opt/gradle/bin:$PATH
# ============================================================
# 9. GitHub CLI (gh)
# ============================================================
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
&& chmod go+r /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/*
# ============================================================
# 10. yq (Mike Farah, Go binary)
# ============================================================
RUN ARCH="$(dpkg --print-architecture)" \
&& curl -fsSL "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${ARCH}" -o /usr/local/bin/yq \
&& chmod +x /usr/local/bin/yq
# ============================================================
# 11. shfmt (Go binary)
# ============================================================
RUN ARCH="$(dpkg --print-architecture)" \
&& curl -fsSL "https://github.com/mvdan/sh/releases/latest/download/shfmt_v3.10.0_linux_${ARCH}" -o /usr/local/bin/shfmt \
&& chmod +x /usr/local/bin/shfmt
# ============================================================
# 12. Create non-root user `crafter` (uid 1000) with passwordless sudo
# and prepare workspace / cache / data dirs
# ============================================================
RUN useradd -m -u 1000 -s /bin/bash crafter \
&& echo "crafter ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/crafter \
&& chmod 0440 /etc/sudoers.d/crafter \
&& mkdir -p /workspace /caches/cargo /caches/maven /caches/gradle /caches/npm /caches/pip /caches/bun /data \
&& chown -R crafter:crafter /workspace /caches /data
# ============================================================
# 13. Switch to crafter for user-scoped installs
# ============================================================
USER crafter
WORKDIR /home/crafter
# Cache env (point tools at the persisted cache dirs)
ENV CARGO_HOME=/caches/cargo \
RUSTUP_HOME=/home/crafter/.rustup \
GRADLE_USER_HOME=/caches/gradle \
MAVEN_OPTS="-Dmaven.repo.local=/caches/maven" \
NPM_CONFIG_CACHE=/caches/npm \
PIP_CACHE_DIR=/caches/pip \
BUN_INSTALL=/home/crafter/.bun \
PIPX_HOME=/home/crafter/.local/pipx \
PIPX_BIN_DIR=/home/crafter/.local/bin
ENV PATH=/home/crafter/.local/bin:/caches/cargo/bin:/home/crafter/.bun/bin:$PATH
# ============================================================
# 14. Rust (rustup, stable) + cargo-audit + cargo-deny
# ============================================================
RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path \
&& /caches/cargo/bin/rustup component add clippy rustfmt \
&& /caches/cargo/bin/cargo install cargo-audit --locked \
&& /caches/cargo/bin/cargo install cargo-deny --locked
# ============================================================
# 15. Bun (curl install)
# ============================================================
RUN curl -fsSL https://bun.sh/install | bash
# ============================================================
# 16. Python user tooling: pipx-managed CLI tools
# ============================================================
RUN python3 -m pip install --user --break-system-packages --no-cache-dir pipx \
&& python3 -m pipx ensurepath \
&& pipx install uv \
&& pipx install ruff \
&& pipx install mypy \
&& pipx install pytest \
&& pipx install pip-audit \
&& pipx install semgrep
# ============================================================
# 17. Go user tooling: govulncheck + staticcheck
# ============================================================
RUN go install golang.org/x/vuln/cmd/govulncheck@latest \
&& go install honnef.co/go/tools/cmd/staticcheck@latest
# Make GOPATH bin discoverable for the crafter user
ENV PATH=/home/crafter/go/bin:$PATH \
GOPATH=/home/crafter/go
# ============================================================
# 18. Ruby user tooling: bundler-audit, rubocop
# ============================================================
RUN gem install --user-install --no-document bundler-audit rubocop || \
sudo gem install --no-document bundler-audit rubocop
ENV PATH=/home/crafter/.local/share/gem/ruby/3.1.0/bin:$PATH
# ============================================================
# 19. PHP user tooling: phpstan, phpunit (composer global)
# ============================================================
ENV COMPOSER_HOME=/home/crafter/.composer
RUN composer global require --no-interaction phpstan/phpstan phpunit/phpunit
ENV PATH=/home/crafter/.composer/vendor/bin:$PATH
# ============================================================
# 20. Smoke script — bake in
# ============================================================
COPY --chown=crafter:crafter smoke.sh /usr/local/bin/smoke.sh
USER root
RUN chmod +x /usr/local/bin/smoke.sh
USER crafter
# ============================================================
# 21. Final ENV / WORKDIR / CMD
# ============================================================
WORKDIR /workspace
CMD ["/bin/bash"]

View file

@ -1,3 +1,88 @@
# crafting-table # crafting-table
Polyglot dev/build/audit container with autonomous patch loop + email digest. Recipes for every Sulkta repo, structured findings back to clawdforge. Polyglot dev/build/audit container — the build farm for the Sulkta ecosystem.
## What this is
A single Docker container with every toolchain we work with, used as a
reliable place to compile / test / audit any Sulkta repo regardless of
where the caller is — agents, Claude sessions, ad-hoc curl, scheduled cron.
Eventual surface (v0.1 full): HTTP API + MCP server + project registry +
job runner + structured findings + email digest + autonomous patch loop
through clawdforge.
Spec: `Sulkta-Coop/openclaw-workspace/memory/spec-crafting-table.md` (LAN-only).
## Status — v0.1 step 1 of 10
- [x] Step 1: Dockerfile + per-language smoke
- [ ] Step 2: SQLite ledger + project registry
- [ ] Step 3: HTTP API skeleton (FastAPI, port 8810)
- [ ] Step 4: Job runner core (asyncio worker pool)
- [ ] Step 5: Per-language parsers (Rust / Python / Go / TS first)
- [ ] Step 6: Findings extraction + storage
- [ ] Step 7: MCP server (stdio JSON-RPC, 8 tools)
- [ ] Step 8: Email digest scheduler
- [ ] Step 9: Autonomous patch loop (clawdforge integration)
- [ ] Step 10: Production recipes — clawdforge, cauldron, tradecraft
## Toolchains in v0.1
| Lang | Versions / extras |
|----------|--------------------------------------------------------------------|
| Python | 3.11 (Debian default) + uv, pipx, pip-audit, ruff, mypy, pytest, semgrep |
| Node | 22.11.0 LTS + npm, pnpm, tsx, eslint, typescript |
| Bun | latest (rolling) |
| Go | 1.22.10 + govulncheck, staticcheck |
| Rust | stable (rustup) + clippy, rustfmt, cargo-audit, cargo-deny |
| Ruby | 3.1 (Debian default) + bundler, bundler-audit, rubocop |
| PHP | 8.2 (Debian default) + composer, phpstan, phpunit |
| JDK | 17 (default) + 21 (Temurin, alongside via `JAVA_HOME_21`) |
| Maven | 3.x (Debian) |
| Gradle | 8.10 |
| .NET | 8.0 SDK |
| Swift | 5.9.2 (Ubuntu 22.04 tarball — works on Debian bookworm) |
| Kotlin | 1.9.25 (compiler) |
| C/C++ | clang + lld + cmake + ninja + valgrind |
| Bash | bash + shellcheck + bats + shfmt |
| Generic | git, jq, yq, ripgrep, fd, gh-cli, curl, wget |
## Build + smoke
```bash
docker network inspect sulkta >/dev/null 2>&1 || docker network create sulkta
docker compose build
docker compose up
# expect: "=== ALL TOOLCHAINS GREEN ===" then exit 0
```
The smoke compiles + runs a hello-world in every language. If it exits 0,
the image is good.
## Image notes
- Base: `debian:bookworm-slim`. Swift uses the upstream Ubuntu 22.04 tarball
which links against bookworm's libicu/libstdc++ baseline.
- Runs as non-root user `crafter` (uid 1000) with passwordless sudo.
- Volume mount points: `/workspace`, `/caches/{cargo,maven,gradle,npm,pip,bun}`,
`/data`. Compose binds these to named volumes so they survive `compose down`.
- Network: external `sulkta` bridge (same one clawdforge + cauldron use).
Create with `docker network create sulkta` if missing.
- Image size baseline is large (8-15 GB expected). Per spec: that's fine.
## Layout
```
.
├── Dockerfile # monolith image with all toolchains
├── compose.yml # build + run-smoke wiring
├── smoke.sh # per-language hello-world test, baked in at /usr/local/bin/smoke.sh
├── README.md
├── LICENSE # MIT
└── .gitignore
```
## License
MIT

28
compose.yml Normal file
View file

@ -0,0 +1,28 @@
# crafting-table v0.1 — step 1 compose.
#
# Builds the monolith image and runs the smoke test once.
# In step 2+ the `command:` is replaced with the API server entrypoint.
name: crafting-table
services:
crafting-table:
build: .
image: crafting-table:local
container_name: crafting-table
command: ["/usr/local/bin/smoke.sh"]
user: crafter
working_dir: /workspace
volumes:
- workspace:/workspace
- caches:/caches
- data:/data
networks: [sulkta]
volumes:
workspace:
caches:
data:
networks:
sulkta:
external: true

194
smoke.sh Executable file
View file

@ -0,0 +1,194 @@
#!/bin/bash
# crafting-table v0.1 step 1 — toolchain smoke test.
#
# Runs INSIDE the built image; prints `--version` and a hello-world for
# every advertised toolchain. Exits 0 only if every block succeeds.
#
# Spec: memory/spec-crafting-table.md (toolchain matrix)
set -eo pipefail
echo "=== crafting-table smoke ==="
echo "user: $(id)"
echo "pwd: $(pwd)"
echo
echo "--- python"
python3 --version
python3 -c "print('hello from python')"
uv --version
ruff --version
mypy --version
pytest --version
pip-audit --version
semgrep --version
echo
echo "--- node"
node --version
node -e "console.log('hello from node')"
npm --version
pnpm --version
tsx --version || true
echo
echo "--- bun"
bun --version
bun -e "console.log('hello from bun')"
echo
echo "--- go"
go version
mkdir -p /tmp/smoke-go && cd /tmp/smoke-go && go mod init smoke 2>/dev/null || true
cat >main.go <<'EOF'
package main
func main() { println("hello from go") }
EOF
go run main.go
cd / && rm -rf /tmp/smoke-go
govulncheck -version 2>&1 | head -2 || true
staticcheck -version
echo
echo "--- rust"
rustc --version
cargo --version
cargo audit --version
cargo deny --version
cat >/tmp/smoke.rs <<'EOF'
fn main() { println!("hello from rust"); }
EOF
rustc /tmp/smoke.rs -o /tmp/smoke && /tmp/smoke
rm /tmp/smoke /tmp/smoke.rs
echo
echo "--- ruby"
ruby --version
ruby -e "puts 'hello from ruby'"
bundler --version
bundle-audit version 2>&1 | head -1 || true
rubocop --version
echo
echo "--- php"
php --version | head -1
php -r "echo 'hello from php', PHP_EOL;"
composer --version
phpstan --version 2>&1 | head -1
phpunit --version | head -1
echo
echo "--- jdk 17 (default)"
java -version 2>&1 | head -1
javac -version
cat >/tmp/Smoke.java <<'EOF'
public class Smoke { public static void main(String[] a){ System.out.println("hello from java 17"); } }
EOF
javac /tmp/Smoke.java -d /tmp && java -cp /tmp Smoke
rm /tmp/Smoke.java /tmp/Smoke.class
echo
echo "--- jdk 21 (alongside)"
"$JAVA_HOME_21/bin/java" -version 2>&1 | head -1
"$JAVA_HOME_21/bin/javac" -version
cat >/tmp/Smoke21.java <<'EOF'
public class Smoke21 { public static void main(String[] a){ System.out.println("hello from java 21"); } }
EOF
"$JAVA_HOME_21/bin/javac" /tmp/Smoke21.java -d /tmp && "$JAVA_HOME_21/bin/java" -cp /tmp Smoke21
rm /tmp/Smoke21.java /tmp/Smoke21.class
echo
echo "--- maven"
mvn -version | head -1
echo
echo "--- gradle"
gradle -version 2>&1 | grep -E '^Gradle ' | head -1
echo
echo "--- kotlin"
kotlinc -version 2>&1 | head -1
cat >/tmp/smoke.kt <<'EOF'
fun main() { println("hello from kotlin") }
EOF
kotlinc /tmp/smoke.kt -include-runtime -d /tmp/smoke.jar 2>/dev/null
java -jar /tmp/smoke.jar
rm /tmp/smoke.kt /tmp/smoke.jar
echo
echo "--- .net 8"
dotnet --version
mkdir -p /tmp/smoke-dotnet && cd /tmp/smoke-dotnet
dotnet new console -o app -n Smoke --force >/dev/null
cd app
# Suppress NuGet first-run noise; just run the hello-world.
dotnet run --nologo 2>&1 | tail -3
cd / && rm -rf /tmp/smoke-dotnet
echo
echo "--- swift"
swift --version 2>&1 | head -1
cat >/tmp/smoke.swift <<'EOF'
print("hello from swift")
EOF
swift /tmp/smoke.swift
rm /tmp/smoke.swift
echo
echo "--- clang/c"
clang --version | head -1
cat >/tmp/smoke.c <<'EOF'
#include <stdio.h>
int main(void){puts("hello from c");return 0;}
EOF
clang /tmp/smoke.c -o /tmp/smoke-c && /tmp/smoke-c
rm /tmp/smoke.c /tmp/smoke-c
echo
echo "--- clang++/cpp"
clang++ --version | head -1
cat >/tmp/smoke.cpp <<'EOF'
#include <iostream>
int main(){std::cout<<"hello from cpp"<<std::endl;return 0;}
EOF
clang++ -std=c++17 /tmp/smoke.cpp -o /tmp/smoke-cpp && /tmp/smoke-cpp
rm /tmp/smoke.cpp /tmp/smoke-cpp
echo
echo "--- cmake + ninja"
cmake --version | head -1
ninja --version
echo
echo "--- valgrind"
valgrind --version
echo
echo "--- bash + shellcheck + bats + shfmt"
bash --version | head -1
shellcheck --version | head -2 | tail -1
bats --version
shfmt --version
cat >/tmp/smoke.sh <<'EOF'
#!/bin/bash
echo "hello from bash"
EOF
chmod +x /tmp/smoke.sh
bash /tmp/smoke.sh
shellcheck /tmp/smoke.sh && echo "shellcheck clean"
rm /tmp/smoke.sh
echo
echo "--- generic tools"
git --version
jq --version
rg --version | head -1
fd --version
gh --version | head -1
yq --version
curl --version | head -1
wget --version | head -1
echo
echo "=== ALL TOOLCHAINS GREEN ==="