Install pinned skills from Git repositories
This commit is contained in:
@@ -34,13 +34,31 @@ Install into one project instead of the user catalogue:
|
|||||||
ink-skills install --project /path/to/project configure-ink-agent
|
ink-skills install --project /path/to/project configure-ink-agent
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer is intentionally smaller than `npx skills`: no registry, package
|
Install from any Git repository your normal Git credentials can read:
|
||||||
manager, network access, copies, prompts, lockfile, or hidden state. It creates
|
|
||||||
absolute symlinks from `$HOME/.ink/skills` (or the installer-only
|
```sh
|
||||||
|
ink-skills add --ref main git@git.example:team/skills.git
|
||||||
|
ink-skills add https://git.example/team/skills.git skills/review-sql
|
||||||
|
```
|
||||||
|
|
||||||
|
`add` resolves the ref to one commit, exports it into a content-addressed store,
|
||||||
|
computes a SHA-256 over each selected skill tree, and symlinks that immutable
|
||||||
|
artifact. Provenance is recorded next to installed links in `.ink-skills.tsv`:
|
||||||
|
source, requested ref, resolved commit, path, and SHA-256. It deliberately does
|
||||||
|
not edit `SKILL.md` comments. A moved branch does not silently update an installed
|
||||||
|
skill; the existing pin causes a visible provenance collision.
|
||||||
|
|
||||||
|
Git SSH agents and credential helpers remain Git's job. Credential-bearing HTTP
|
||||||
|
URLs are refused so secrets do not enter manifests or process listings. Remote
|
||||||
|
skill trees containing symlinks are also refused.
|
||||||
|
|
||||||
|
The local `install` path is intentionally smaller than `npx skills`: no registry,
|
||||||
|
package manager, network access, copies, prompts, lockfile, or hidden state. It
|
||||||
|
creates absolute symlinks from `$HOME/.ink/skills` (or the installer-only
|
||||||
`$INK_SKILLS_HOME` target override) to this checkout. Pulling the repository
|
`$INK_SKILLS_HOME` target override) to this checkout. Pulling the repository
|
||||||
updates installed skills; restarting Ink freezes the new bytes into the next
|
updates locally installed skills; restarting Ink freezes the new bytes into the
|
||||||
startup snapshot. Ink itself discovers `$HOME/.ink/skills`, `$CWD/.ink/skills`,
|
next startup snapshot. Ink itself discovers `$HOME/.ink/skills`,
|
||||||
and colon-separated `INK_SKILLS_DIRS`.
|
`$CWD/.ink/skills`, and colon-separated `INK_SKILLS_DIRS`.
|
||||||
|
|
||||||
`ink-skills list` emits TSV. `ink-skills --help` is the complete command manual.
|
`ink-skills list` emits TSV. `ink-skills --help` is the complete command manual.
|
||||||
Existing paths and foreign symlinks are refused rather than overwritten.
|
Existing paths and foreign symlinks are refused rather than overwritten.
|
||||||
@@ -85,7 +103,7 @@ sh test/skills-smoke.sh
|
|||||||
## Refusals
|
## Refusals
|
||||||
|
|
||||||
- No npm package merely to create symlinks.
|
- No npm package merely to create symlinks.
|
||||||
- No skill registry or update daemon.
|
- No skill registry, automatic updater, or network daemon.
|
||||||
- No policy mutation during installation.
|
- No policy mutation during installation.
|
||||||
- No bundled binaries; those belong in `toolset`.
|
- No bundled binaries; those belong in `toolset`.
|
||||||
- No automatic installation by Ink itself.
|
- No automatic installation by Ink itself.
|
||||||
|
|||||||
+217
-49
@@ -5,27 +5,40 @@ usage() {
|
|||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
usage: ink-skills list
|
usage: ink-skills list
|
||||||
ink-skills install [--user | --project DIR] [SKILL ...]
|
ink-skills install [--user | --project DIR] [SKILL ...]
|
||||||
|
ink-skills add [--user | --project DIR] [--ref REF] REPOSITORY [PATH ...]
|
||||||
|
|
||||||
Install Ink skills from this checkout as symlinks.
|
Install Ink skills as symlinks from this checkout or a pinned Git artifact.
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
list List available skill names and source paths as TSV.
|
list List skills in this checkout as TSV.
|
||||||
install Link named skills; with no names, link every skill.
|
install Link named local skills; with no names, link every skill.
|
||||||
|
add Fetch REPOSITORY, pin REF to a commit, materialize an
|
||||||
|
immutable snapshot, and link skill PATHs from it. PATH
|
||||||
|
defaults to every skills/*/SKILL.md directory.
|
||||||
|
|
||||||
Targets:
|
Targets:
|
||||||
--user $INK_SKILLS_HOME or $HOME/.ink/skills (default)
|
--user $INK_SKILLS_HOME or $HOME/.ink/skills (default)
|
||||||
--project DIR DIR/.ink/skills
|
--project DIR DIR/.ink/skills
|
||||||
|
|
||||||
|
Git storage:
|
||||||
|
$INK_SKILLS_STORE or $XDG_DATA_HOME/ink-skills, otherwise
|
||||||
|
$HOME/.local/share/ink-skills. Git, tar, and sha256sum are required by `add`.
|
||||||
|
Authentication belongs to Git's SSH agent or credential helper; credentialed
|
||||||
|
HTTP URLs are refused. Installed provenance is written to
|
||||||
|
TARGET/.ink-skills.tsv without modifying SKILL.md.
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
TSV with SKILL, TARGET, and ACTION columns.
|
TSV with SKILL, TARGET, ACTION, SOURCE, COMMIT, and SHA256 columns.
|
||||||
|
|
||||||
Exit status:
|
Exit status:
|
||||||
0 success; 2 usage error; 3 target collision or invalid skill.
|
0 success; 2 usage error; 3 collision, invalid source, or fetch failure.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
ink-skills list
|
ink-skills list
|
||||||
ink-skills install audit-ink-cli configure-ink-agent
|
ink-skills install audit-ink-cli configure-ink-agent
|
||||||
ink-skills install --project . create-ink-tool
|
ink-skills install --project . create-ink-tool
|
||||||
|
ink-skills add --ref main git@git.example:team/skills.git
|
||||||
|
ink-skills add https://git.example/team/skills.git skills/review-sql
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +47,13 @@ die() {
|
|||||||
exit 3
|
exit 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reject_record_breaks() {
|
||||||
|
case $1 in
|
||||||
|
*" "*|*"
|
||||||
|
"*) die "tabs and newlines are not allowed: $1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
|
||||||
repo_dir=$(CDPATH= cd -- "$script_dir/.." && pwd -P)
|
repo_dir=$(CDPATH= cd -- "$script_dir/.." && pwd -P)
|
||||||
skills_dir=$repo_dir/skills
|
skills_dir=$repo_dir/skills
|
||||||
@@ -47,11 +67,110 @@ list_skills() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select_target() {
|
||||||
|
case $target_mode in
|
||||||
|
user)
|
||||||
|
if [ -n "${INK_SKILLS_HOME:-}" ]; then
|
||||||
|
target=$INK_SKILLS_HOME
|
||||||
|
else
|
||||||
|
[ -n "${HOME:-}" ] || die 'HOME is unset; set HOME or INK_SKILLS_HOME'
|
||||||
|
target=$HOME/.ink/skills
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
project)
|
||||||
|
project=$(CDPATH= cd -- "$target_arg" 2>/dev/null && pwd -P) || die "project directory not found: $target_arg"
|
||||||
|
target=$project/.ink/skills
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
mkdir -p -- "$target"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_target_option() {
|
||||||
|
case $1 in
|
||||||
|
--user)
|
||||||
|
target_mode=user
|
||||||
|
shift_count=1
|
||||||
|
;;
|
||||||
|
--project)
|
||||||
|
[ "$#" -ge 2 ] || {
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
target_mode=project
|
||||||
|
target_arg=$2
|
||||||
|
shift_count=2
|
||||||
|
;;
|
||||||
|
*) shift_count=0 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
frontmatter_name() {
|
||||||
|
sed -n 's/^name:[[:space:]]*//p' "$1/SKILL.md" | sed -n '1p'
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_skill_dir() {
|
||||||
|
source_path=$1
|
||||||
|
[ -d "$source_path" ] && [ -f "$source_path/SKILL.md" ] || die "not a skill directory: $source_path"
|
||||||
|
[ ! -L "$source_path" ] && [ ! -L "$source_path/SKILL.md" ] || die "skill root and SKILL.md must not be symlinks: $source_path"
|
||||||
|
skill_name=$(frontmatter_name "$source_path")
|
||||||
|
[ -n "$skill_name" ] || die "missing frontmatter name: $source_path/SKILL.md"
|
||||||
|
[ "$skill_name" = "$(basename -- "$source_path")" ] || die "frontmatter name does not match directory: $source_path"
|
||||||
|
case $skill_name in
|
||||||
|
''|.*|*/*) die "invalid skill name: $skill_name" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
link_skill() {
|
||||||
|
skill_name=$1
|
||||||
|
source_path=$2
|
||||||
|
source_label=$3
|
||||||
|
commit=$4
|
||||||
|
digest=$5
|
||||||
|
destination=$target/$skill_name
|
||||||
|
if [ -L "$destination" ]; then
|
||||||
|
linked=$(readlink "$destination")
|
||||||
|
[ "$linked" = "$source_path" ] || die "refusing foreign symlink: $destination -> $linked"
|
||||||
|
action=unchanged
|
||||||
|
elif [ -e "$destination" ]; then
|
||||||
|
die "refusing existing path: $destination"
|
||||||
|
else
|
||||||
|
ln -s -- "$source_path" "$destination"
|
||||||
|
action=linked
|
||||||
|
fi
|
||||||
|
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$skill_name" "$destination" "$action" "$source_label" "$commit" "$digest"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_source_record() {
|
||||||
|
skill_name=$1
|
||||||
|
source_label=$2
|
||||||
|
ref=$3
|
||||||
|
commit=$4
|
||||||
|
skill_path=$5
|
||||||
|
digest=$6
|
||||||
|
manifest=$target/.ink-skills.tsv
|
||||||
|
record_needed=yes
|
||||||
|
[ -e "$manifest" ] || return 0
|
||||||
|
if awk -F '\t' -v skill="$skill_name" 'NR > 1 && $1 == skill { found = 1 } END { exit !found }' "$manifest"; then
|
||||||
|
existing=$(awk -F '\t' -v skill="$skill_name" 'NR > 1 && $1 == skill { print $0; exit }' "$manifest")
|
||||||
|
wanted=$(printf '%s\t%s\t%s\t%s\t%s\t%s' "$skill_name" "$source_label" "$ref" "$commit" "$skill_path" "$digest")
|
||||||
|
[ "$existing" = "$wanted" ] || die "provenance collision for installed skill: $skill_name"
|
||||||
|
record_needed=no
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
append_source_record() {
|
||||||
|
[ "$record_needed" = yes ] || return 0
|
||||||
|
manifest=$target/.ink-skills.tsv
|
||||||
|
if [ ! -e "$manifest" ]; then
|
||||||
|
printf 'SKILL\tSOURCE\tREF\tCOMMIT\tPATH\tSHA256\n' >"$manifest"
|
||||||
|
fi
|
||||||
|
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$skill_name" "$repository" "$ref" "$commit" "$skill_path" "$digest" >>"$manifest"
|
||||||
|
}
|
||||||
|
|
||||||
[ "$#" -gt 0 ] || {
|
[ "$#" -gt 0 ] || {
|
||||||
usage >&2
|
usage >&2
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
command=$1
|
command=$1
|
||||||
shift
|
shift
|
||||||
case $command in
|
case $command in
|
||||||
@@ -67,7 +186,7 @@ case $command in
|
|||||||
list_skills
|
list_skills
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
install) ;;
|
install|add) ;;
|
||||||
*)
|
*)
|
||||||
usage >&2
|
usage >&2
|
||||||
exit 2
|
exit 2
|
||||||
@@ -76,19 +195,20 @@ esac
|
|||||||
|
|
||||||
target_mode=user
|
target_mode=user
|
||||||
target_arg=
|
target_arg=
|
||||||
|
ref=HEAD
|
||||||
while [ "$#" -gt 0 ]; do
|
while [ "$#" -gt 0 ]; do
|
||||||
|
parse_target_option "$@"
|
||||||
|
if [ "$shift_count" -gt 0 ]; then
|
||||||
|
shift "$shift_count"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
case $1 in
|
case $1 in
|
||||||
--user)
|
--ref)
|
||||||
target_mode=user
|
[ "$command" = add ] && [ "$#" -ge 2 ] || {
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--project)
|
|
||||||
[ "$#" -ge 2 ] || {
|
|
||||||
usage >&2
|
usage >&2
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
target_mode=project
|
ref=$2
|
||||||
target_arg=$2
|
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--)
|
--)
|
||||||
@@ -102,50 +222,98 @@ while [ "$#" -gt 0 ]; do
|
|||||||
*) break ;;
|
*) break ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
select_target
|
||||||
|
|
||||||
case $target_mode in
|
printf 'SKILL\tTARGET\tACTION\tSOURCE\tCOMMIT\tSHA256\n'
|
||||||
user)
|
|
||||||
if [ -n "${INK_SKILLS_HOME:-}" ]; then
|
|
||||||
target=$INK_SKILLS_HOME
|
|
||||||
else
|
|
||||||
[ -n "${HOME:-}" ] || die 'HOME is unset; set HOME or INK_SKILLS_HOME'
|
|
||||||
target=$HOME/.ink/skills
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
project)
|
|
||||||
project=$(CDPATH= cd -- "$target_arg" 2>/dev/null && pwd -P) || die "project directory not found: $target_arg"
|
|
||||||
target=$project/.ink/skills
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
mkdir -p -- "$target"
|
if [ "$command" = install ]; then
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
if [ "$#" -eq 0 ]; then
|
|
||||||
set --
|
set --
|
||||||
for path in "$skills_dir"/*; do
|
for path in "$skills_dir"/*; do
|
||||||
[ -d "$path" ] || continue
|
[ -d "$path" ] || continue
|
||||||
[ -f "$path/SKILL.md" ] || continue
|
[ -f "$path/SKILL.md" ] || continue
|
||||||
set -- "$@" "$(basename -- "$path")"
|
set -- "$@" "$(basename -- "$path")"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
for skill do
|
||||||
printf 'SKILL\tTARGET\tACTION\n'
|
|
||||||
for skill do
|
|
||||||
case $skill in
|
case $skill in
|
||||||
''|.*|*/*) die "invalid skill name: $skill" ;;
|
''|.*|*/*) die "invalid skill name: $skill" ;;
|
||||||
esac
|
esac
|
||||||
source=$skills_dir/$skill
|
source_path=$skills_dir/$skill
|
||||||
[ -d "$source" ] && [ -f "$source/SKILL.md" ] || die "unknown skill: $skill"
|
validate_skill_dir "$source_path"
|
||||||
destination=$target/$skill
|
link_skill "$skill_name" "$source_path" local - -
|
||||||
if [ -L "$destination" ]; then
|
done
|
||||||
linked=$(readlink "$destination")
|
exit 0
|
||||||
[ "$linked" = "$source" ] || die "refusing foreign symlink: $destination -> $linked"
|
fi
|
||||||
action=unchanged
|
|
||||||
elif [ -e "$destination" ]; then
|
[ "$#" -gt 0 ] || {
|
||||||
die "refusing existing path: $destination"
|
usage >&2
|
||||||
else
|
exit 2
|
||||||
ln -s -- "$source" "$destination"
|
}
|
||||||
action=linked
|
repository=$1
|
||||||
|
shift
|
||||||
|
reject_record_breaks "$repository"
|
||||||
|
reject_record_breaks "$ref"
|
||||||
|
case $ref in
|
||||||
|
-*) die "invalid ref: $ref" ;;
|
||||||
|
esac
|
||||||
|
case $repository in
|
||||||
|
http://*@*|https://*@*) die 'credentialed HTTP URLs are refused; use an SSH agent or Git credential helper' ;;
|
||||||
|
esac
|
||||||
|
command -v git >/dev/null 2>&1 || die 'git is required by add'
|
||||||
|
command -v tar >/dev/null 2>&1 || die 'tar is required by add'
|
||||||
|
command -v sha256sum >/dev/null 2>&1 || die 'sha256sum is required by add'
|
||||||
|
|
||||||
|
if [ -n "${INK_SKILLS_STORE:-}" ]; then
|
||||||
|
store=$INK_SKILLS_STORE
|
||||||
|
elif [ -n "${XDG_DATA_HOME:-}" ]; then
|
||||||
|
store=$XDG_DATA_HOME/ink-skills
|
||||||
|
else
|
||||||
|
[ -n "${HOME:-}" ] || die 'HOME is unset; set HOME, XDG_DATA_HOME, or INK_SKILLS_STORE'
|
||||||
|
store=$HOME/.local/share/ink-skills
|
||||||
|
fi
|
||||||
|
repo_key=$(printf '%s' "$repository" | git hash-object --stdin) || die 'cannot hash repository identity'
|
||||||
|
mirror=$store/git/$repo_key.git
|
||||||
|
mkdir -p -- "$store/git" "$store/artifacts/$repo_key"
|
||||||
|
if [ ! -d "$mirror" ]; then
|
||||||
|
git clone --quiet --mirror -- "$repository" "$mirror" || die "cannot clone repository: $repository"
|
||||||
|
fi
|
||||||
|
git --git-dir="$mirror" fetch --quiet --force origin "$ref" || die "cannot fetch ref: $ref"
|
||||||
|
commit=$(git --git-dir="$mirror" rev-parse --verify 'FETCH_HEAD^{commit}') || die "ref does not resolve to a commit: $ref"
|
||||||
|
artifact=$store/artifacts/$repo_key/$commit
|
||||||
|
if [ ! -d "$artifact" ]; then
|
||||||
|
temporary=$artifact.tmp.$$
|
||||||
|
rm -rf -- "$temporary"
|
||||||
|
mkdir -p -- "$temporary"
|
||||||
|
if ! git --git-dir="$mirror" archive "$commit" | tar -x -C "$temporary"; then
|
||||||
|
rm -rf -- "$temporary"
|
||||||
|
die "cannot materialize commit: $commit"
|
||||||
fi
|
fi
|
||||||
printf '%s\t%s\t%s\n' "$skill" "$destination" "$action"
|
mv -- "$temporary" "$artifact"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
|
set --
|
||||||
|
for source_path in "$artifact"/skills/*; do
|
||||||
|
[ -d "$source_path" ] || continue
|
||||||
|
[ -f "$source_path/SKILL.md" ] || continue
|
||||||
|
set -- "$@" "skills/$(basename -- "$source_path")"
|
||||||
|
done
|
||||||
|
[ "$#" -gt 0 ] || die "repository has no skills/*/SKILL.md at $commit"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for skill_path do
|
||||||
|
reject_record_breaks "$skill_path"
|
||||||
|
case $skill_path in
|
||||||
|
''|/*|*:*|..|../*|*/../*|*/..) die "invalid repository skill path: $skill_path" ;;
|
||||||
|
esac
|
||||||
|
source_path=$artifact/$skill_path
|
||||||
|
validate_skill_dir "$source_path"
|
||||||
|
if find "$source_path" -type l -print | grep . >/dev/null 2>&1; then
|
||||||
|
die "remote skill contains symlinks: $skill_path"
|
||||||
|
fi
|
||||||
|
digest=$(git --git-dir="$mirror" archive "$commit:$skill_path" | sha256sum | awk '{print $1}') || die "cannot hash skill artifact: $skill_path"
|
||||||
|
check_source_record "$skill_name" "$repository" "$ref" "$commit" "$skill_path" "$digest"
|
||||||
|
link_skill "$skill_name" "$source_path" "$repository" "$commit" "$digest"
|
||||||
|
append_source_record
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -30,4 +30,53 @@ HOME=$tmp/home $repo/bin/ink-skills install --project "$tmp/project" create-ink-
|
|||||||
[ -L "$tmp/project/.ink/skills/create-ink-tool" ]
|
[ -L "$tmp/project/.ink/skills/create-ink-tool" ]
|
||||||
grep "create-ink-tool.*linked" "$tmp/project.tsv" >/dev/null
|
grep "create-ink-tool.*linked" "$tmp/project.tsv" >/dev/null
|
||||||
|
|
||||||
|
remote=$tmp/remote
|
||||||
|
mkdir -p "$remote/skills/remote-review"
|
||||||
|
cat >"$remote/skills/remote-review/SKILL.md" <<'EOF'
|
||||||
|
---
|
||||||
|
name: remote-review
|
||||||
|
description: Review one remote fixture.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Remote review
|
||||||
|
EOF
|
||||||
|
git -C "$remote" init -q
|
||||||
|
git -C "$remote" config user.email ink-skills@example.invalid
|
||||||
|
git -C "$remote" config user.name 'Ink Skills Test'
|
||||||
|
git -C "$remote" add skills/remote-review/SKILL.md
|
||||||
|
git -C "$remote" commit -qm fixture
|
||||||
|
commit=$(git -C "$remote" rev-parse HEAD)
|
||||||
|
|
||||||
|
mkdir -p "$tmp/remote-home"
|
||||||
|
HOME=$tmp/remote-home INK_SKILLS_STORE=$tmp/store $repo/bin/ink-skills add --ref HEAD "$remote" skills/remote-review >"$tmp/add.tsv"
|
||||||
|
remote_link=$tmp/remote-home/.ink/skills/remote-review
|
||||||
|
[ -L "$remote_link" ]
|
||||||
|
case $(readlink "$remote_link") in
|
||||||
|
"$tmp/store/artifacts/"*"/$commit/skills/remote-review") ;;
|
||||||
|
*) echo 'remote link is not pinned to resolved commit' >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
grep "remote-review.*linked.*$commit" "$tmp/add.tsv" >/dev/null
|
||||||
|
manifest=$tmp/remote-home/.ink/skills/.ink-skills.tsv
|
||||||
|
[ "$(wc -l <"$manifest")" -eq 2 ]
|
||||||
|
awk -F '\t' -v commit="$commit" 'NR == 2 { exit !($1 == "remote-review" && $4 == commit && length($6) == 64) }' "$manifest"
|
||||||
|
|
||||||
|
HOME=$tmp/remote-home INK_SKILLS_STORE=$tmp/store $repo/bin/ink-skills add --ref HEAD "$remote" skills/remote-review >"$tmp/readd.tsv"
|
||||||
|
grep 'remote-review.*unchanged' "$tmp/readd.tsv" >/dev/null
|
||||||
|
[ "$(wc -l <"$manifest")" -eq 2 ]
|
||||||
|
|
||||||
|
printf '\nchanged\n' >>"$remote/skills/remote-review/SKILL.md"
|
||||||
|
git -C "$remote" add skills/remote-review/SKILL.md
|
||||||
|
git -C "$remote" commit -qm changed
|
||||||
|
if HOME=$tmp/remote-home INK_SKILLS_STORE=$tmp/store $repo/bin/ink-skills add --ref HEAD "$remote" skills/remote-review >/dev/null 2>"$tmp/pin.err"; then
|
||||||
|
echo 'expected provenance collision after ref moves' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
grep 'provenance collision' "$tmp/pin.err" >/dev/null
|
||||||
|
|
||||||
|
if HOME=$tmp/remote-home INK_SKILLS_STORE=$tmp/store $repo/bin/ink-skills add 'https://user:secret@example.invalid/skills.git' >/dev/null 2>"$tmp/credential.err"; then
|
||||||
|
echo 'expected credentialed URL refusal' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
grep 'credentialed HTTP URLs are refused' "$tmp/credential.err" >/dev/null
|
||||||
|
|
||||||
printf 'ok\n'
|
printf 'ok\n'
|
||||||
|
|||||||
Reference in New Issue
Block a user