`${VAR:-{}}` appends a stray '}' when VAR is set (bash brace-matching), so the
JSON handed to a caller skill's setup.sh was corrupted and its jq failed with
"Unmatched '}'" — install-caller-skills.sh then caught the non-zero exit and
skipped the skill. Default SECRETS_JSON in two safe steps and pass it as a plain
var. This is why node1-ssh never installed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
4.1 KiB
Bash
71 lines
4.1 KiB
Bash
#!/usr/bin/env bash
|
|
# Install CALLER-PROVIDED opencode skills — the framework's skill-plugin hook.
|
|
#
|
|
# The reusable workflow ships a few built-in skills (gitea-api, gitea-admin). A consuming repo can
|
|
# add its OWN, repo-specific skills (e.g. a homelab "ssh into the deploy host" skill) without any
|
|
# change to this framework: it commits them under `.gitea/agent-skills/<name>/` in its own repo.
|
|
# This step discovers them in the checked-out caller workspace and installs the ones allowed for the
|
|
# running agent. That keeps deploy-target / infra specifics in the repo they belong to, not here.
|
|
#
|
|
# Layout the framework expects, per skill, in the CALLER repo:
|
|
# .gitea/agent-skills/<name>/
|
|
# SKILL.md (required) — the opencode Skill doc; copied verbatim into the skill registry.
|
|
# skill.json (required) — {"agents":["senior","lead"]} — which agents may load this skill.
|
|
# setup.sh (optional) — runtime setup (e.g. write an SSH alias). Runs ONLY when this agent is
|
|
# allowed the skill. Receives $SECRETS_JSON (all inherited secrets, as JSON) and must
|
|
# extract what it needs via jq; it must no-op cleanly if its secrets aren't set.
|
|
#
|
|
# Required env (provided by the workflow step): NAME WORKSPACE SECRETS_JSON
|
|
# (SECRETS_JSON = toJSON(secrets); passed so a caller's setup.sh can read repo-specific secrets
|
|
# whose names this framework cannot know in advance.)
|
|
#
|
|
# IMPORTANT — caller-skill secrets read from SECRETS_JSON MUST be single-line. The runner masks a
|
|
# secret's value in logs by exact match, but toJSON(secrets) escapes newlines to '\n', so a MULTILINE
|
|
# secret (e.g. a raw PEM key) no longer matches the mask and would print in cleartext in the step's
|
|
# "expression evaluated to …" log line. Store multiline values base64-encoded (single-line) and
|
|
# decode them inside setup.sh. Single-line values mask correctly.
|
|
set -eu
|
|
|
|
# Safe default for SECRETS_JSON (see note at the setup.sh call below re: the ${x:-{}} brace bug).
|
|
SJ="${SECRETS_JSON:-}"; [ -n "$SJ" ] || SJ='{}'
|
|
DIR="${WORKSPACE:-$GITHUB_WORKSPACE}/.gitea/agent-skills"
|
|
CFG="$HOME/.config/opencode/opencode.json"
|
|
[ -d "$DIR" ] || { echo "no caller skills (.gitea/agent-skills/ absent) — nothing to install"; exit 0; }
|
|
|
|
allow='{}' # skills to flip to "allow" in permission.skill for THIS agent
|
|
for skill_dir in "$DIR"/*/; do
|
|
[ -d "$skill_dir" ] || continue
|
|
name=$(basename "$skill_dir")
|
|
md="$skill_dir/SKILL.md"; meta="$skill_dir/skill.json"
|
|
if [ ! -f "$md" ] || [ ! -f "$meta" ]; then
|
|
echo "caller skill '$name': missing SKILL.md or skill.json — skipping"; continue
|
|
fi
|
|
# Is this agent allowed the skill?
|
|
if ! jq -e --arg n "$NAME" '(.agents // []) | index($n)' "$meta" >/dev/null 2>&1; then
|
|
echo "caller skill '$name': not allowed for @$NAME — skipping"; continue
|
|
fi
|
|
# Install the doc.
|
|
dest="$HOME/.config/opencode/skills/$name"
|
|
mkdir -p "$dest" && chmod 700 "$dest"
|
|
cp "$md" "$dest/SKILL.md"
|
|
chmod -R o=rX "$dest"
|
|
# Optional runtime setup, with all inherited secrets available as JSON (never printed here).
|
|
# NOTE: pass SECRETS_JSON via a plain variable — do NOT inline ${SECRETS_JSON:-{}} here or in
|
|
# setup.sh: bash brace-matching appends a stray '}' when the var is set, corrupting the JSON so
|
|
# the skill's `jq` fails ("Unmatched '}'") and the skill is silently skipped.
|
|
if [ -f "$skill_dir/setup.sh" ]; then
|
|
echo "caller skill '$name': running setup.sh for @$NAME"
|
|
SECRETS_JSON="$SJ" NAME="$NAME" WORKSPACE="${WORKSPACE:-$GITHUB_WORKSPACE}" \
|
|
bash "$skill_dir/setup.sh" || { echo "caller skill '$name': setup.sh failed — skipping this skill"; continue; }
|
|
fi
|
|
allow=$(jq -nc --argjson a "$allow" --arg n "$name" '$a + {($n):"allow"}')
|
|
echo "caller skill '$name': installed + allowed for @$NAME"
|
|
done
|
|
|
|
# Merge the allowed caller skills into the permission allow-list opencode already wrote.
|
|
if [ "$allow" != '{}' ] && [ -f "$CFG" ]; then
|
|
tmp=$(mktemp)
|
|
jq --argjson add "$allow" '.permission.skill = ((.permission.skill // {}) + $add)' "$CFG" > "$tmp" && mv "$tmp" "$CFG"
|
|
echo "permission.skill updated with caller skills: $(jq -c '.permission.skill' "$CFG")"
|
|
fi
|