diff --git a/README.md b/README.md index 2b1a870..e2fdf70 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A collection of [Dev Container Features](https://containers.dev/implementors/fea - [Mistral Vibe](#mistral-vibe) - [Tree-sitter](#tree-sitter) - [RTK](#rtk) + - [ZPM](#zpm) - [AWF CLI](#awf-cli) - [Repository Structure](#repository-structure) - [Local Testing](#local-testing) @@ -236,6 +237,43 @@ RTK supports Linux containers on both `x86_64` (amd64) and `aarch64` (arm64) arc The feature runs `rtk init --global` during installation, setting up the global configuration for the container user. +### ZPM + +Installs [ZPM](https://github.com/awf-project/zpm), a Zig-based MCP server exposing a Trealla Prolog logic engine over STDIO for deterministic logical reasoning. Provides 29 MCP tools for knowledge management, logical querying, and truth maintenance. + +```jsonc +// devcontainer.json +{ + "features": { + "ghcr.io/awf-project/devcontainer-features/zpm:1": {} + } +} +``` + +#### Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `version` | string | `latest` | Version to install: `latest` or a specific version (e.g. `v0.3.0`) | + +#### Examples + +Pin a specific version: + +```jsonc +{ + "features": { + "ghcr.io/awf-project/devcontainer-features/zpm:1": { + "version": "v0.3.0" + } + } +} +``` + +#### Architecture Support + +ZPM supports Linux containers on both `x86_64` (amd64) and `aarch64` (arm64) architectures. The feature automatically detects the container architecture and downloads the appropriate binary. SHA256 checksums are verified on every install. + ### AWF CLI > **Private package** — This feature requires access to the [awf-project/cli](https://github.com/awf-project/cli) private repository via the `gh` CLI. Only the latest version is available (active development). @@ -289,6 +327,9 @@ src/ tree-sitter/ # Tree-sitter feature devcontainer-feature.json install.sh + zpm/ # ZPM feature + devcontainer-feature.json + install.sh test/ flutter/ # Flutter tests scenarios.json @@ -305,6 +346,9 @@ test/ tree-sitter/ # Tree-sitter tests scenarios.json test.sh + zpm/ # ZPM tests + scenarios.json + test.sh .github/ workflows/ release.yml # CI/CD: test on PR, publish on tag diff --git a/src/zpm/devcontainer-feature.json b/src/zpm/devcontainer-feature.json new file mode 100644 index 0000000..57d3c4d --- /dev/null +++ b/src/zpm/devcontainer-feature.json @@ -0,0 +1,18 @@ +{ + "id": "zpm", + "version": "1.0.0", + "name": "ZPM", + "description": "Installs ZPM, a Zig-based MCP server exposing Trealla Prolog for deterministic logical reasoning.", + "documentationURL": "https://github.com/awf-project/zpm", + "licenseURL": "https://github.com/awf-project/zpm/blob/main/LICENSE", + "options": { + "version": { + "type": "string", + "default": "latest", + "description": "ZPM version to install (e.g., 'v0.3.0'). Use 'latest' for the most recent release." + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} diff --git a/src/zpm/install.sh b/src/zpm/install.sh new file mode 100755 index 0000000..a88bd1f --- /dev/null +++ b/src/zpm/install.sh @@ -0,0 +1,81 @@ +#!/bin/bash +set -e + +# --- Options injected by devcontainer feature engine --- +VERSION="${VERSION:-latest}" + +echo "==> ZPM feature: version=${VERSION}" + +# Ensure curl is available +if ! command -v curl &>/dev/null || ! command -v sha256sum &>/dev/null; then + echo "==> Installing missing dependencies..." + apt-get update -y + apt-get install -y --no-install-recommends curl ca-certificates coreutils + apt-get clean && rm -rf /var/lib/apt/lists/* +fi + +# Detect architecture +ARCH=$(uname -m) +case "$ARCH" in + x86_64|amd64) ARCH_SUFFIX="x86_64" ;; + aarch64|arm64) ARCH_SUFFIX="arm64" ;; + *) + echo "ERROR: unsupported architecture: $ARCH" + exit 1 + ;; +esac +echo "==> Detected architecture: ${ARCH} (${ARCH_SUFFIX})" + +# Resolve version +if [ "$VERSION" = "latest" ]; then + VERSION=$(curl -fsSL "https://api.github.com/repos/awf-project/zpm/releases/latest" \ + | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/') + if [ -z "$VERSION" ]; then + echo "ERROR: failed to resolve latest ZPM version from GitHub API" + exit 1 + fi + echo "==> Resolved latest version: ${VERSION}" +fi + +# Ensure version has 'v' prefix (GitHub tags use v0.3.0 format) +case "$VERSION" in + v*) ;; + *) VERSION="v${VERSION}" ;; +esac + +# Download binary and checksums +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR"' EXIT + +ARTIFACT="zpm-linux-${ARCH_SUFFIX}" +DOWNLOAD_URL="https://github.com/awf-project/zpm/releases/download/${VERSION}/${ARTIFACT}" +CHECKSUMS_URL="https://github.com/awf-project/zpm/releases/download/${VERSION}/SHA256SUMS" + +echo "==> Downloading ${DOWNLOAD_URL}..." +curl -fsSL "$DOWNLOAD_URL" -o "${TMP_DIR}/${ARTIFACT}" + +echo "==> Downloading SHA256SUMS..." +curl -fsSL "$CHECKSUMS_URL" -o "${TMP_DIR}/SHA256SUMS" + +# Verify SHA256 checksum +echo "==> Verifying checksum..." +EXPECTED_SUM=$(grep " ${ARTIFACT}$" "${TMP_DIR}/SHA256SUMS" | awk '{print $1}') +if [ -z "$EXPECTED_SUM" ]; then + echo "ERROR: artifact ${ARTIFACT} not found in SHA256SUMS" + exit 1 +fi + +ACTUAL_SUM=$(sha256sum "${TMP_DIR}/${ARTIFACT}" | awk '{print $1}') +if [ "$EXPECTED_SUM" != "$ACTUAL_SUM" ]; then + echo "ERROR: SHA256 checksum mismatch" + echo " expected: ${EXPECTED_SUM}" + echo " actual: ${ACTUAL_SUM}" + exit 1 +fi +echo "==> Checksum verified" + +# Install binary +install -m 0755 "${TMP_DIR}/${ARTIFACT}" /usr/local/bin/zpm + +echo "==> ZPM $(zpm version 2>&1) installed at $(command -v zpm)" +echo "==> ZPM feature install complete" diff --git a/test/zpm/install_zpm_latest.sh b/test/zpm/install_zpm_latest.sh new file mode 100755 index 0000000..1e00752 --- /dev/null +++ b/test/zpm/install_zpm_latest.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +# Scenario: install latest ZPM + +source "$(dirname "$0")/test.sh" diff --git a/test/zpm/install_zpm_specific_version.sh b/test/zpm/install_zpm_specific_version.sh new file mode 100755 index 0000000..4e61edc --- /dev/null +++ b/test/zpm/install_zpm_specific_version.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +# Scenario: install a specific ZPM version (v0.3.0) + +echo "==> Testing ZPM specific version scenario..." + +# zpm binary must be on PATH +if ! command -v zpm &>/dev/null; then + echo "FAIL: zpm not found on PATH" + exit 1 +fi +echo "PASS: zpm on PATH ($(command -v zpm))" + +# Verify the exact version matches +ZPM_VERSION=$(zpm version 2>&1) +if [[ "$ZPM_VERSION" != *"0.3.0"* ]]; then + echo "FAIL: expected version 0.3.0, got: ${ZPM_VERSION}" + exit 1 +fi +echo "PASS: zpm version matches 0.3.0 => ${ZPM_VERSION}" + +echo "==> ZPM specific version tests passed" diff --git a/test/zpm/scenarios.json b/test/zpm/scenarios.json new file mode 100644 index 0000000..b33660c --- /dev/null +++ b/test/zpm/scenarios.json @@ -0,0 +1,16 @@ +{ + "install_zpm_latest": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "zpm": {} + } + }, + "install_zpm_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "zpm": { + "version": "v0.3.0" + } + } + } +} diff --git a/test/zpm/test.sh b/test/zpm/test.sh new file mode 100755 index 0000000..5d4a76e --- /dev/null +++ b/test/zpm/test.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +echo "==> Testing ZPM feature..." + +# zpm binary must be on PATH +if ! command -v zpm &>/dev/null; then + echo "FAIL: zpm not found on PATH" + exit 1 +fi +echo "PASS: zpm on PATH ($(command -v zpm))" + +# zpm --version must succeed and return a version string +ZPM_VERSION=$(zpm version 2>&1) +if [ -z "$ZPM_VERSION" ]; then + echo "FAIL: zpm --version returned empty output" + exit 1 +fi +echo "PASS: zpm --version => ${ZPM_VERSION}" + +echo "==> All ZPM feature tests passed"