diff --git a/.github/skills/skill-installer/SKILL.md b/.github/skills/skill-installer/SKILL.md index 8990cd8..fc8b9b9 100644 --- a/.github/skills/skill-installer/SKILL.md +++ b/.github/skills/skill-installer/SKILL.md @@ -22,7 +22,7 @@ Use this skill to install another skill from a local path, a directory, or a Git - Local `SKILL.md` file - Local skill directory -- GitHub blob URL to a `SKILL.md` file +- GitHub blob URL to a `SKILL.md` file, including sibling supporting files ## Install script @@ -50,5 +50,7 @@ The installer prompts for: 1. Source path or URL 2. Target scope: repo or global 3. Skill name if it cannot be inferred -4. Whether to copy supporting files +4. Whether to copy supporting files for a local directory source 5. Whether to overwrite an existing install + +For GitHub blob URLs, supporting files in the same skill directory are downloaded automatically. diff --git a/.github/skills/skill-installer/scripts/install-skill.sh b/.github/skills/skill-installer/scripts/install-skill.sh index 089185c..ab3b8ee 100755 --- a/.github/skills/skill-installer/scripts/install-skill.sh +++ b/.github/skills/skill-installer/scripts/install-skill.sh @@ -35,6 +35,16 @@ rewrite_github_blob_url() { fi } +parse_github_blob_url() { + local url="$1" + if [[ "$url" =~ ^https://github\.com/([^/]+)/([^/]+)/blob/([^/]+)/(.*)$ ]]; then + printf '%s\t%s\t%s\t%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" + return 0 + fi + + return 1 +} + extract_skill_name() { local file="$1" sed -n 's/^name:[[:space:]]*//p' "$file" | head -n 1 @@ -46,6 +56,45 @@ download_source() { curl -fsSL "$url" -o "$out" } +list_github_contents() { + local owner="$1" + local repo="$2" + local ref="$3" + local path="$4" + + curl -fsSL -H 'Accept: application/vnd.github+json' \ + "https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${ref}" | \ + python3 -c $'import json,sys\ndata = json.load(sys.stdin)\nitems = data if isinstance(data, list) else [data]\nfor item in items:\n print("%s\\t%s\\t%s" % (item.get("type", ""), item.get("path", ""), item.get("download_url", "")))' +} + +download_github_contents() { + local owner="$1" + local repo="$2" + local ref="$3" + local path="$4" + local target_root="$5" + + while IFS=$'\t' read -r item_type item_path download_url; do + [[ -z "$item_type" ]] && continue + + local relative_path="${item_path#${path}/}" + local destination_path="$target_root/$relative_path" + + case "$item_type" in + dir) + download_github_contents "$owner" "$repo" "$ref" "$item_path" "$target_root" + ;; + file) + mkdir -p "$(dirname "$destination_path")" + download_source "$download_url" "$destination_path" + ;; + *) + printf 'Skipping unsupported GitHub content type: %s (%s)\n' "$item_type" "$item_path" >&2 + ;; + esac + done < <(list_github_contents "$owner" "$repo" "$ref" "$path") +} + copy_skill_dir() { local source_dir="$1" local target_dir="$2" @@ -82,9 +131,16 @@ source_kind="" source_path="$source_input" if [[ "$source_input" =~ ^https?:// ]]; then - source_kind="url" - source_path="$(rewrite_github_blob_url "$source_input")" - download_source "$source_path" "$staging_dir/SKILL.md" + if github_blob_parts="$(parse_github_blob_url "$source_input")"; then + source_kind="github-blob-url" + IFS=$'\t' read -r github_owner github_repo github_ref github_path <<<"$github_blob_parts" + github_skill_dir="$(dirname "$github_path")" + download_github_contents "$github_owner" "$github_repo" "$github_ref" "$github_skill_dir" "$staging_dir" + else + source_kind="url" + source_path="$(rewrite_github_blob_url "$source_input")" + download_source "$source_path" "$staging_dir/SKILL.md" + fi elif [[ -d "$source_input" ]]; then source_kind="directory" skill_file="$source_input/SKILL.md" @@ -142,4 +198,6 @@ if [[ ! -f "$target_dir/SKILL.md" ]]; then exit 1 fi +echo "<$source_input>" >"$target_dir/source.md" + printf 'Installed %s skill to %s\n' "$skill_name" "$target_dir"