#!/usr/bin/env bash set -euo pipefail REPO_DIR="${REPO_DIR:-$(pwd)}" THIRDPARTY_BRANCH="${THIRDPARTY_BRANCH:-thirdparty/skill}" TARGET_BRANCH="${TARGET_BRANCH:-main}" MANIFEST_PATH="${MANIFEST_PATH:-.gitea/ci/thirdparty_skills.json}" COMMIT_AUTHOR_NAME="${COMMIT_AUTHOR_NAME:-ci[bot]}" COMMIT_AUTHOR_EMAIL="${COMMIT_AUTHOR_EMAIL:-ci-bot@local}" emit_sources_tsv() { python3 - "$MANIFEST_PATH" <<'PY' import json import sys with open(sys.argv[1], encoding="utf-8") as fh: data = json.load(fh) for entry in data["sources"]: print( "\x1f".join( [ entry["id"], entry["snapshot_dir"], entry["sync_mode"], entry["source_list"], entry.get("skills_subdir", ""), entry.get("output_name", entry["id"]), entry.get("platform_config", ""), entry.get("template_root", ""), entry.get("data_dir", ""), entry.get("scripts_dir", ""), ] ) ) PY } render_codex_skill() { local snapshot_root="$1" local output_dir="$2" local platform_config_rel="$3" local template_root_rel="$4" local data_dir_rel="$5" local scripts_dir_rel="$6" python3 - "$snapshot_root" "$output_dir" "$platform_config_rel" "$template_root_rel" "$data_dir_rel" "$scripts_dir_rel" <<'PY' import json import pathlib import shutil import sys snapshot_root = pathlib.Path(sys.argv[1]) output_dir = pathlib.Path(sys.argv[2]) platform_config_path = snapshot_root / sys.argv[3] template_root = snapshot_root / sys.argv[4] data_dir = snapshot_root / sys.argv[5] scripts_dir = snapshot_root / sys.argv[6] config = json.loads(platform_config_path.read_text(encoding="utf-8")) skill_template = (template_root / "base" / "skill-content.md").read_text(encoding="utf-8") quick_reference = "" if config.get("sections", {}).get("quickReference"): quick_reference = "\n" + (template_root / "base" / "quick-reference.md").read_text(encoding="utf-8") def render_frontmatter(frontmatter): if not frontmatter: return "" lines = ["---"] for key, value in frontmatter.items(): if any(ch in value for ch in ':"\n'): value = value.replace('"', '\\"') lines.append(f'{key}: "{value}"') else: lines.append(f"{key}: {value}") lines.extend(["---", ""]) return "\n".join(lines) content = skill_template content = content.replace("{{TITLE}}", config["title"]) content = content.replace("{{DESCRIPTION}}", config["description"]) content = content.replace("{{SCRIPT_PATH}}", config["scriptPath"]) content = content.replace("{{SKILL_OR_WORKFLOW}}", config["skillOrWorkflow"]) content = content.replace("{{QUICK_REFERENCE}}", quick_reference) if output_dir.exists(): shutil.rmtree(output_dir) output_dir.mkdir(parents=True, exist_ok=True) (output_dir / "SKILL.md").write_text( render_frontmatter(config.get("frontmatter")) + content, encoding="utf-8", ) if data_dir.exists(): shutil.copytree(data_dir, output_dir / "data") if scripts_dir.exists(): shutil.copytree(scripts_dir, output_dir / "scripts") PY } tracked_skill_exists() { local name="$1" if git ls-files --error-unmatch -- "codex/skills/$name" >/dev/null 2>&1; then return 0 fi return 1 } cd "$REPO_DIR" git config user.name "$COMMIT_AUTHOR_NAME" git config user.email "$COMMIT_AUTHOR_EMAIL" git fetch origin "$THIRDPARTY_BRANCH" git fetch origin "$TARGET_BRANCH" tmp_dir="$(mktemp -d)" cleanup() { rm -rf "$tmp_dir" } trap cleanup EXIT git checkout -B "$TARGET_BRANCH" "origin/$TARGET_BRANCH" mkdir -p "codex/skills/.sources" sources_file="$tmp_dir/sources.tsv" if ! emit_sources_tsv > "$sources_file"; then echo "ERROR: failed to load third-party manifest: $MANIFEST_PATH" >&2 exit 1 fi while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_subdir output_name platform_config template_root data_dir scripts_dir; do [ -n "$source_id" ] || continue if [ -f "$source_list" ]; then while IFS= read -r name; do [ -n "$name" ] || continue rm -rf "codex/skills/$name" done < "$source_list" fi done < "$sources_file" declare -A owners=() while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_subdir output_name platform_config template_root data_dir scripts_dir; do [ -n "$source_id" ] || continue git archive --format=tar "origin/${THIRDPARTY_BRANCH}" "$snapshot_dir" | tar -xf - -C "$tmp_dir" snapshot_root="$tmp_dir/$snapshot_dir" names=() case "$sync_mode" in copy_skill_dirs) source_skills_dir="$snapshot_root/$skills_subdir" if [ ! -d "$source_skills_dir" ]; then echo "ERROR: $skills_subdir not found in snapshot $snapshot_dir" >&2 exit 1 fi for dir in "$source_skills_dir"/*; do [ -d "$dir" ] || continue name="$(basename "$dir")" if [ -n "${owners[$name]:-}" ] && [ "${owners[$name]}" != "$source_id" ]; then echo "ERROR: duplicate third-party skill name: $name" >&2 exit 1 fi if [ -d "codex/skills/$name" ] && tracked_skill_exists "$name"; then echo "ERROR: skill name conflict with tracked skill: $name" >&2 exit 1 fi rm -rf "codex/skills/$name" cp -R "$dir" "codex/skills/$name" names+=("$name") owners["$name"]="$source_id" done ;; render_codex_skill) name="$output_name" if [ -n "${owners[$name]:-}" ] && [ "${owners[$name]}" != "$source_id" ]; then echo "ERROR: duplicate third-party skill name: $name" >&2 exit 1 fi if [ -d "codex/skills/$name" ] && tracked_skill_exists "$name"; then echo "ERROR: skill name conflict with tracked skill: $name" >&2 exit 1 fi render_codex_skill "$snapshot_root" "codex/skills/$name" "$platform_config" "$template_root" "$data_dir" "$scripts_dir" names+=("$name") owners["$name"]="$source_id" ;; *) echo "ERROR: unsupported sync mode: $sync_mode" >&2 exit 1 ;; esac printf "%s\n" "${names[@]}" | sort > "$source_list" done < "$sources_file" git add codex/skills if git diff --cached --quiet; then echo "No third-party skills to sync." exit 0 fi git commit -m ":package: deps(skills): sync thirdparty skills" TOKEN="${WORKFLOW:-}" if [ -n "$TOKEN" ] && [ -n "${GITHUB_SERVER_URL:-}" ] && [ -n "${GITHUB_REPOSITORY:-}" ]; then git remote set-url origin "https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git" fi git push origin "$TARGET_BRANCH"