diff --git a/commands/install.md b/commands/install.md index a1d611e..9cc65f7 100644 --- a/commands/install.md +++ b/commands/install.md @@ -1,7 +1,7 @@ --- name: skill-git:install -description: Install a skill from SkillHub (skills.sh) or ClawHub (clawhub.ai). Triggers on "install skill", "download skill from", or when called from /skill-git:search after the user confirms an install. Usage: /skill-git:install skillhub: or /skill-git:install clawhub:. -argument-hint: skillhub: | clawhub: [-a ] +description: Install a skill from SkillHub (skills.sh), ClawHub (clawhub.ai), or any git URL (public or private — including your own GitHub/GitLab/self-hosted). Triggers on "install skill", "download skill from", or when called from /skill-git:search after the user confirms an install. Usage: /skill-git:install skillhub: | clawhub: | git:[@] | git:[@]. +argument-hint: skillhub: | clawhub: | git:[@] [-a ] allowed-tools: Bash(bash *), AskUserQuestion --- @@ -37,6 +37,7 @@ If `$ARGUMENTS` is empty: Identifier required. Usage: /skill-git:install skillhub:/@ /skill-git:install clawhub: + /skill-git:install git:/[@] (also accepts a full git URL) Use /skill-git:search to find skills first. ``` @@ -46,7 +47,11 @@ Extract `registry` and `identifier` from the first non-flag token: - `skillhub:/@` → `registry = "skillhub"`, validate `identifier` contains `/` and `@` - `clawhub:` → `registry = "clawhub"`, validate slug is non-empty with no `/` or spaces -- Unknown prefix → `Unknown registry. Use "skillhub:" or "clawhub:" as prefix.` Stop. +- `git:` → `registry = "git"`. The spec is either: + - `/` shorthand (expanded to `https://github.com//.git`; HTTPS works with gh's credential helper for private repos), or + - a full git URL (`https://...`, `http://...`, `git@host:...`, `ssh://...`) + - and may end with `@` to override the auto-derived name +- Unknown prefix → `Unknown registry. Use "skillhub:", "clawhub:", or "git:" as prefix.` Stop. On validation failure, extract a best-effort search query from all non-flag tokens (e.g. `vercel/agent-browser` → `agent-browser`) and run a registry search to suggest the correct identifier: @@ -74,6 +79,7 @@ When the user selects a result, use that as the new `identifier` and `registry` Derive `install_name` immediately: - SkillHub: the `` part after `@` - ClawHub: the `` +- Git: parse the spec by checking if it ends in `@` where `` matches `[A-Za-z0-9_][A-Za-z0-9._-]*` (the suffix contains no `/` or `:`). If so, that suffix is `install_name` and the prefix is the URL. Otherwise the URL is the whole spec and `install_name` is the URL basename with any `.git` suffix stripped (e.g. `tianyilt/skill-paper-write` → `skill-paper-write`, `git@gitlab.com:foo/bar.git` → `bar`). Extract optional flags: - `-a `: target agent (`claude`, `gemini`, `codex`, `openclaw`). Pass through to prelude. @@ -181,7 +187,32 @@ Could not write SKILL.md to . ``` Stop. -### 3c. Validate +### 3c. Git URL + +For `registry = "git"`, hand the parsed spec to the helper script. It normalizes shorthand to a full URL and clones into `install_path`: + +```bash +bash "${CLAUDE_PLUGIN_ROOT}/scripts/sg-install-git.sh" "" "" 2>&1 +``` + +`` is the URL portion derived in Step 0 (i.e. without the optional trailing `@`). The script preserves the cloned `.git` and its history; `sg-init.sh` (Step 5) will detect the existing repo and either reuse its `v*` tags (Case D — already versioned) or add a `v1.0.0` tag at the first commit (Case C). Origin remote is left intact so the user can `git pull` updates later. + +If the command succeeds and `/SKILL.md` exists: +- Set `skill_source_dir` = `` (files already in place) +- Read `/SKILL.md` as `downloaded_content` + +If the command fails or `SKILL.md` is not found: +``` +Could not download skill from git URL. + URL: + + The repository may not exist, you may not have access, or the repo may not contain a SKILL.md at its root. + Verify access with: git ls-remote + For private GitHub repos, ensure your SSH key or gh auth is configured. +``` +Stop. + +### 3d. Validate Verify `downloaded_content` is non-empty and starts with `---`. If not: ``` @@ -206,7 +237,7 @@ Display the skill preview: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Install preview - Source : + Source : > Name : Install : / Desc : @@ -248,7 +279,7 @@ Installed ✅ v1.0.0 Path : /SKILL.md - From : + From : > Desc : Next steps: diff --git a/scripts/sg-install-git.sh b/scripts/sg-install-git.sh new file mode 100755 index 0000000..1033798 --- /dev/null +++ b/scripts/sg-install-git.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# sg-install-git.sh — clone a skill from any git URL into the install path. +# +# Used by /skill-git:install when registry = "git". +# Accepts shorthand "owner/repo" (defaults to GitHub SSH) or any full git URL +# (https://, http://, ssh://, git@host:...). The cloned `.git` and its history +# are preserved so /skill-git:init (Step 5 of install) can re-tag idempotently +# (Case C/D in sg-init.sh) and so the user can `git pull` updates from origin +# later. +# +# Usage: +# sg-install-git.sh + +set -euo pipefail + +if [ $# -lt 2 ]; then + echo "[skill-git] Error: sg-install-git.sh requires " >&2 + exit 1 +fi + +url="$1" +install_path="$2" + +# Shorthand "/" → HTTPS GitHub URL by default. HTTPS works for +# public repos out of the box and for private repos when the user has run +# `gh auth login` (gh registers a credential helper for git on first auth). +# Users who prefer SSH can pass the full URL: git:git@github.com:owner/repo.git +if [[ "$url" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*/[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]]; then + url="https://github.com/${url}.git" +fi + +# Refuse to clone over a non-empty target — preserves existing skill folders. +if [ -e "$install_path" ] && [ -n "$(ls -A "$install_path" 2>/dev/null || true)" ]; then + echo "[skill-git] Error: install path already exists and is non-empty: $install_path" >&2 + exit 1 +fi + +mkdir -p "$(dirname "$install_path")" + +# Clone with full history. Origin remote is set automatically by `git clone`. +if ! git clone "$url" "$install_path"; then + echo "[skill-git] Error: git clone failed for $url" >&2 + exit 1 +fi + +# Sanity check: a skill must have a SKILL.md at the repo root. +if [ ! -f "$install_path/SKILL.md" ]; then + echo "[skill-git] Error: cloned repo has no SKILL.md at root: $install_path" >&2 + exit 1 +fi + +echo "[skill-git] Cloned $url → $install_path"