#!/usr/bin/env bash set -euo pipefail readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" readonly MANIFEST_PATH="${REPO_ROOT}/scripts/skill-bundles.json" BUILD_DIR="" usage() { cat <<'EOF' Usage: package_skill_runtimes.sh plan package_skill_runtimes.sh validate package_skill_runtimes.sh package The bundle manifest is package-oriented. Bundles in state=planned are skipped by the package command until their package-owned entrypoints exist. EOF } require_command() { local cmd="$1" if ! command -v "${cmd}" >/dev/null 2>&1; then printf 'missing required command: %s\n' "${cmd}" >&2 exit 1 fi } emit_bundles() { node -e ' const fs = require("node:fs"); const manifestPath = process.argv[1]; const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); for (const bundle of manifest.bundles ?? []) { const row = [ bundle.skill, bundle.type, bundle.runtimePackage, bundle.entrypoint, bundle.output, bundle.buildState ].join("\t"); process.stdout.write(`${row}\n`); } ' "${MANIFEST_PATH}" } cleanup() { if [[ -n "${BUILD_DIR}" && -d "${BUILD_DIR}" ]]; then rm -rf "${BUILD_DIR}" fi } plan() { printf 'skill bundle plan from %s\n' "${MANIFEST_PATH}" while IFS=$'\t' read -r skill type runtime_package entrypoint output build_state; do printf -- '- skill=%s type=%s state=%s runtime=%s entrypoint=%s output=%s\n' \ "${skill}" "${type}" "${build_state}" "${runtime_package}" "${entrypoint}" "${output}" done < <(emit_bundles) } validate() { local failures=0 while IFS=$'\t' read -r skill type runtime_package entrypoint output build_state; do local runtime_path="${REPO_ROOT}/${runtime_package#./}" local entrypoint_path="${REPO_ROOT}/${entrypoint#./}" local output_path="${REPO_ROOT}/${output}" if [[ "${type}" != "go-binary" ]]; then printf 'invalid bundle type for %s: %s\n' "${skill}" "${type}" >&2 failures=1 fi if [[ ! -d "${runtime_path}" ]]; then printf 'missing runtime package dir for %s: %s\n' "${skill}" "${runtime_package}" >&2 failures=1 fi if [[ ! -f "${runtime_path}/go.mod" ]]; then printf 'missing go.mod for %s runtime package: %s\n' "${skill}" "${runtime_package}" >&2 failures=1 fi if [[ "${build_state}" != "planned" && ! -d "${entrypoint_path}" ]]; then printf 'missing ready entrypoint for %s: %s\n' "${skill}" "${entrypoint}" >&2 failures=1 fi if [[ "${output}" != skills/* ]]; then printf 'output for %s must stay under skills/: %s\n' "${skill}" "${output}" >&2 failures=1 fi if [[ "${build_state}" != "planned" && ! -d "$(dirname "${output_path}")" ]]; then printf 'missing output parent for %s: %s\n' "${skill}" "$(dirname "${output}")" >&2 failures=1 fi done < <(emit_bundles) if [[ "${failures}" -ne 0 ]]; then exit 1 fi printf 'skill bundle manifest validated\n' } package_bundles() { validate require_command go require_command install require_command mktemp BUILD_DIR="$(mktemp -d "${TMPDIR:-/tmp}/skill-runtimes.XXXXXX")" trap cleanup EXIT INT TERM while IFS=$'\t' read -r skill type runtime_package entrypoint output build_state; do if [[ "${build_state}" != "ready" ]]; then printf 'skipping %s (%s)\n' "${skill}" "${build_state}" continue fi local output_path="${REPO_ROOT}/${output}" local artifact_path="${BUILD_DIR}/${skill}" printf 'building %s from %s\n' "${skill}" "${entrypoint}" ( cd "${REPO_ROOT}" go build -trimpath -o "${artifact_path}" "${entrypoint}" ) mkdir -p "$(dirname "${output_path}")" install -m 0755 "${artifact_path}" "${output_path}" printf 'installed %s\n' "${output}" done < <(emit_bundles) } main() { require_command node local command="${1:-}" case "${command}" in plan) plan ;; validate) validate ;; package) package_bundles ;; ""|-h|--help|help) usage ;; *) printf 'unknown command: %s\n' "${command}" >&2 usage >&2 exit 1 ;; esac } main "$@"