Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
18 changes: 18 additions & 0 deletions src/zpm/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
81 changes: 81 additions & 0 deletions src/zpm/install.sh
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 6 additions & 0 deletions test/zpm/install_zpm_latest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
set -e

# Scenario: install latest ZPM

source "$(dirname "$0")/test.sh"
23 changes: 23 additions & 0 deletions test/zpm/install_zpm_specific_version.sh
Original file line number Diff line number Diff line change
@@ -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"
16 changes: 16 additions & 0 deletions test/zpm/scenarios.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
21 changes: 21 additions & 0 deletions test/zpm/test.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading