diff --git a/build.sh b/build.sh index a9ef60e6..9d0af1d9 100755 --- a/build.sh +++ b/build.sh @@ -127,7 +127,7 @@ fi echo "==> clawdie-iso build" echo " ISO : ${ISO_VERSION}-${BUILD_CHANNEL} (zot ${ZOT_RESOLVED_VERSION})" echo " FreeBSD : ${FREEBSD_VERSION} ${FREEBSD_ARCH}" -echo " Clawdie : ${CLAWDIE_REF}" +echo " Clawdie : CLI phased out — Colibri is the control plane" echo " Desktop : ${DEFAULT_DESKTOP}" echo " Pkg : ${DEFAULT_PKG_BRANCH}" echo " GPU : ${GPU_DRIVER:-auto-detect}" @@ -304,13 +304,7 @@ resolve_colibri_paths() { fi } -resolve_clawdie_ai_repo() { - _resolved_clawdie_ai_repo="${CLAWDIE_AI_REPO:-/home/clawdie/ai/clawdie-ai}" - case "${_resolved_clawdie_ai_repo}" in - /*) ;; - *) _resolved_clawdie_ai_repo="${SCRIPT_DIR}/${_resolved_clawdie_ai_repo}" ;; - esac -} +# clawdie-ai retired — Colibri is the control plane. preflight_colibri_artifacts() { [ "${FEATURE_COLIBRI:-NO}" = "YES" ] || return 0 @@ -419,54 +413,6 @@ json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' } -resolve_clawdie_commit() { - _ref="$1" - _repo="https://code.smilepowered.org/clawdie/clawdie-ai.git" - if printf '%s' "$_ref" | grep -Eq '^[0-9a-fA-F]{40}$'; then - printf '%s\n' "$_ref" - return 0 - fi - if command -v git >/dev/null 2>&1; then - git ls-remote "$_repo" \ - "refs/heads/${_ref}" \ - "refs/tags/${_ref}^{}" \ - "refs/tags/${_ref}" 2>/dev/null \ - | awk ' - $2 ~ /\^\{\}$/ { print $1; found = 1; exit } - first == "" { first = $1 } - END { if (!found && first != "") print first } - ' - fi -} - -resolve_latest_clawdie_tag() { - _repo_api="https://code.smilepowered.org/api/v1/repos/clawdie/clawdie-ai" - _repo_git="https://code.smilepowered.org/clawdie/clawdie-ai.git" - - _tag=$( - curl -fsS "${_repo_api}/releases?limit=20" 2>/dev/null \ - | grep -o '"tag_name":"[^"]*"' \ - | head -1 \ - | cut -d'"' -f4 - ) - if [ -n "$_tag" ]; then - printf '%s\n' "$_tag" - return 0 - fi - - git ls-remote --tags "$_repo_git" 2>/dev/null \ - | awk -F/ '$2 == "tags" && $3 ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ { print $3 }' \ - | sed 's/^v//' \ - | sort -t. -k1,1n -k2,2n -k3,3n \ - | tail -n 1 \ - | sed 's/^/v/' -} - -is_pinned_clawdie_ref() { - _ref="$1" - printf '%s' "$_ref" | grep -Eq '^[0-9a-fA-F]{40}$|^v[0-9]+\.[0-9]+\.[0-9]+$' -} - # Assert a repo has no uncommitted *or untracked* changes. `git status --porcelain` # is used (not `git diff`) so stray untracked files — which would change the build # yet pass a diff-only check — also fail the gate. Increments _release_errors on a @@ -492,9 +438,6 @@ check_release_gate() { assert_clean_repo "clawdie-iso" "${SCRIPT_DIR}" - resolve_clawdie_ai_repo - assert_clean_repo "clawdie-ai" "${_resolved_clawdie_ai_repo}" - if [ "${FEATURE_COLIBRI:-NO}" = "YES" ]; then resolve_colibri_paths assert_clean_repo "colibri" "${_resolved_colibri_repo}" @@ -544,17 +487,7 @@ write_build_manifest() { fi # Clawdie-AI provenance: the image stages a git checkout of the AI source, # so record whether the tree is modified at build time. - _clawdie_ai_modified="null" - if command -v git >/dev/null 2>&1; then - resolve_clawdie_ai_repo - if git -C "${_resolved_clawdie_ai_repo}" rev-parse --git-dir >/dev/null 2>&1; then - if [ -z "$(git -C "${_resolved_clawdie_ai_repo}" status --porcelain 2>/dev/null)" ]; then - _clawdie_ai_modified="false" - else - _clawdie_ai_modified="true" - fi - fi - fi + _clawdie_ai_modified="(retired)" if [ -n "${LIVE_SSH_PUBKEY_FP:-}" ]; then _live_ssh_pubkey_fp_json="\"$(json_escape "${LIVE_SSH_PUBKEY_FP}")\"" fi @@ -607,8 +540,8 @@ write_build_manifest() { "build_channel": "$(json_escape "${BUILD_CHANNEL}")", "freebsd_version": "$(json_escape "${FREEBSD_VERSION}")", "freebsd_arch": "$(json_escape "${FREEBSD_ARCH}")", - "clawdie_ai_ref": "$(json_escape "${CLAWDIE_REF}")", - "clawdie_ai_commit": "$(json_escape "${CLAWDIE_AI_COMMIT:-unknown}")", + "clawdie_ai_ref": "(retired)", + "clawdie_ai_commit": "(retired)", "clawdie_ai_modified": ${_clawdie_ai_modified:-null}, "pi_version": "$(json_escape "${_pi_version:-unknown}")", "live_ssh_pubkey_fp": ${_live_ssh_pubkey_fp_json}, @@ -1055,15 +988,15 @@ install_colibri_service() { echo " colibri skills seeded: 6 entries" fi - # Import clawdie-ai skill definitions into the catalog. + # Import colibri skill definitions into the catalog. # Reads .agent/skills/*/SKILL.md and registers name + description. - resolve_clawdie_ai_repo - _clawdie_ai_dir="${_resolved_clawdie_ai_repo}" - if [ -d "${_clawdie_ai_dir}/.agent/skills" ]; then - "${SCRIPT_DIR}/scripts/import-clawdie-skills.sh" \ - "${_clawdie_ai_dir}" "${MOUNT_POINT}" + resolve_colibri_paths + _colibri_dir="${_resolved_colibri_repo}" + if [ -d "${_colibri_dir}/.agent/skills" ]; then + "${SCRIPT_DIR}/scripts/import-colibri-skills.sh" \ + "${_colibri_dir}" "${MOUNT_POINT}" else - echo " clawdie-ai checkout not found, skipping skill import" + echo " colibri .agent/skills not found, skipping skill import" fi } @@ -2265,29 +2198,26 @@ else fi # --- step 4: fetch + prepare Clawdie-AI tarball (offline-ready) --- +# Skip when Colibri is the control plane — the TypeScript AI tarball +# is no longer needed for firstboot. +if [ "${FEATURE_COLIBRI:-NO}" = "YES" ]; then + echo "==> [4/7] Clawdie-AI tarball skipped (FEATURE_COLIBRI=YES — Colibri is the control plane)" +else # Resolve "latest" from Forgejo releases first, then tags. if [ "${CLAWDIE_REF:-${CLAWDIE_VERSION:-}}" = "latest" ] || [ -z "${CLAWDIE_REF:-}" ]; then echo "==> [4/7] Resolving latest Clawdie-AI version..." - CLAWDIE_VERSION=$(resolve_latest_clawdie_tag | sed 's/^v//') + CLAWDIE_VERSION=$(git ls-remote --tags https://code.smilepowered.org/clawdie/clawdie-ai.git 2>/dev/null | awk -F/ '$2 == "tags" && $3 ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ { print $3 }' | sed 's/^v//' | sort -t. -k1,1n -k2,2n -k3,3n | tail -n 1 | sed 's/^/v/') if [ -z "$CLAWDIE_VERSION" ]; then - echo "ERROR: could not resolve latest Clawdie-AI release/tag from Forgejo." - echo " Pin --clawdie-ref main or --clawdie-version X.Y.Z explicitly." + echo "ERROR: could not resolve latest Clawdie-AI release/tag." exit 1 fi CLAWDIE_REF="v${CLAWDIE_VERSION}" echo " Resolved: ${CLAWDIE_REF}" fi - -CLAWDIE_AI_COMMIT=$(resolve_clawdie_commit "$CLAWDIE_REF" | head -n 1) +CLAWDIE_AI_COMMIT=$(git ls-remote https://code.smilepowered.org/clawdie/clawdie-ai.git "refs/heads/${CLAWDIE_REF}" "refs/tags/${CLAWDIE_REF}^{}" "refs/tags/${CLAWDIE_REF}" 2>/dev/null | head -1 | awk '{print $1}') CLAWDIE_AI_COMMIT="${CLAWDIE_AI_COMMIT:-unknown}" echo " Clawdie commit: ${CLAWDIE_AI_COMMIT}" -if [ "$CLAWDIE_AI_COMMIT" = "unknown" ] && [ "$SKIP_PKG_FETCH" -eq 1 ] && ! is_pinned_clawdie_ref "$CLAWDIE_REF"; then - echo "ERROR: cannot safely skip the fetch for moving Clawdie-AI ref '${CLAWDIE_REF}' without resolving its commit." - echo " Run without --skip-fetch / --skip-fetch-pkg, or pin --clawdie-version / --clawdie-ref to a commit." - exit 1 -fi - if [ "$CLAWDIE_AI_COMMIT" != "unknown" ]; then CLAWDIE_ARCHIVE_REF="$CLAWDIE_AI_COMMIT" CLAWDIE_CACHE_KEY="$CLAWDIE_AI_COMMIT" @@ -2369,6 +2299,7 @@ EOF else echo "==> [4/7] Clawdie-AI offline tarball cached." fi +fi # FEATURE_COLIBRI guard (step 4) # Exit here if --fetch-only (CI package pre-fetch step, no root required) if [ "$FETCH_ONLY" -eq 1 ]; then @@ -2612,7 +2543,9 @@ if [ -d "$NPM_GLOBALS_DIR" ]; then echo " Bundled npm-globals: ${NPM_GLOBAL_TARBALL_COUNT} tarballs" fi install_live_npm_globals -cp "${CLAWDIE_TARBALL_ISO}" "${USB_SHARE}/clawdie-ai.tar.gz" +if [ "${FEATURE_COLIBRI:-NO}" != "YES" ]; then + cp "${CLAWDIE_TARBALL_ISO}" "${USB_SHARE}/clawdie-ai.tar.gz" +fi cp "${SCRIPT_DIR}/build.cfg" "${USB_SHARE}/" write_build_manifest "${USB_SHARE}/build-manifest.json" diff --git a/scripts/import-colibri-skills.sh b/scripts/import-colibri-skills.sh new file mode 100755 index 00000000..73cbc6a2 --- /dev/null +++ b/scripts/import-colibri-skills.sh @@ -0,0 +1,81 @@ +#!/bin/sh +# Import colibri skill definitions into the colibri skills catalog. +# +# Reads .agent/skills/*/SKILL.md from a colibri checkout, extracts +# YAML frontmatter (name, description), derives category, and inserts +# into the colibri SQLite coordination database. +# +# Usage during ISO build: +# scripts/import-clawdie-skills.sh /path/to/colibri /path/to/mount-point + +set -eu + +COLIBRI="${1:?usage: $0 }" +MOUNT_POINT="${2:?usage: $0 }" +DB="${MOUNT_POINT}/var/db/colibri/colibri.sqlite" + +if [ ! -d "$COLIBRI/.agent/skills" ]; then + echo " colibri skills dir not found, skipping import" + exit 0 +fi + +if ! command -v sqlite3 >/dev/null 2>&1; then + echo " sqlite3 not available, skipping skills import" + exit 0 +fi + +# Ensure the skills table exists +sqlite3 "$DB" "CREATE TABLE IF NOT EXISTS skills ( + id TEXT PRIMARY KEY NOT NULL, + name TEXT NOT NULL UNIQUE, + description TEXT, + category TEXT, + created_at TEXT NOT NULL +);" 2>/dev/null || true + +NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ) +IMPORTED=0 +SKIPPED=0 + +for skill_md in "$COLIBRI"/.agent/skills/*/SKILL.md; do + [ -f "$skill_md" ] || continue + + # Extract YAML frontmatter between --- markers + frontmatter=$(sed -n '/^---$/,/^---$/p' "$skill_md" | sed '1d;$d') + + # Extract name (first "name:" line after frontmatter) + name=$(echo "$frontmatter" | grep -m1 '^name:' | sed 's/^name: *//' | tr -d '"') + [ -n "$name" ] || continue + + # Extract description + description=$(echo "$frontmatter" | sed -n '/^description:/,/^[a-z]/p' | sed '$d' | sed 's/^description: *//' | tr '\n' ' ' | sed 's/ */ /g' | sed 's/^ *//;s/ *$//') + # Handle multi-line descriptions (| indicator) + if echo "$description" | grep -q '^|$'; then + description=$(echo "$frontmatter" | awk '/^description:/{found=1; next} /^[a-z]/{if(found) exit} found{print}' | tr '\n' ' ' | sed 's/ */ /g' | sed 's/^ *//;s/ *$//') + fi + [ -n "$description" ] || description="$name skill" + + # Derive category from skill name prefix + case "$name" in + git-*) category="git" ;; + db-*|postgres*) category="database" ;; + zfs-*) category="zfs" ;; + add-*) category="integration" ;; + freebsd-*) category="freebsd" ;; + ansible-*) category="automation" ;; + *) category="clawdie" ;; + esac + + # Override category if compatibility field indicates FreeBSD + if echo "$frontmatter" | grep -q 'compatibility:.*FreeBSD'; then + category="freebsd" + fi + + id=$(uuidgen 2>/dev/null || python3 -c "import uuid; print(uuid.uuid4())" 2>/dev/null || echo "skill-$(echo "$name" | md5)") + + sqlite3 "$DB" "INSERT OR IGNORE INTO skills (id, name, description, category, created_at) + VALUES ('$id', '$(echo "$name" | sed "s/'/''/g")', '$(echo "$description" | sed "s/'/''/g")', '$category', '$NOW');" 2>/dev/null && IMPORTED=$((IMPORTED + 1)) || SKIPPED=$((SKIPPED + 1)) +done + +chroot "$MOUNT_POINT" chown colibri:colibri /var/db/colibri/colibri.sqlite 2>/dev/null || true +echo " colibri skills: $IMPORTED imported, $SKIPPED skipped"