#!/bin/sh # LenderSkills installer — https://lenderskills.ai # # Usage: curl -fsSL https://lenderskills.ai/cli | sh -s -- [install-key] # curl -fsSL https://lenderskills.ai/cli | sh -s -- kit: [install-key] # curl -fsSL https://lenderskills.ai/cli | sh -s -- agent: [install-key] # # Writes .claude/skills//SKILL.md in the current directory — one folder # per skill, or one per skill in the kit. Your install key is on any skill page # once you are signed in, and at https://lenderskills.ai/install. Without one the exchange # cannot tell the author who downloaded their work, which is the only thing # they are paid in. A kit installs each skill through its own link, so every # author in it is told, exactly as if you had installed that skill alone. # An agent writes .claude/agents/.md plus every skill it runs, the same way. set -eu ARG="${1:-}" KEY="${2:-${LENDERSKILLS_KEY:-}}" if [ -z "$ARG" ]; then echo "usage: curl -fsSL https://lenderskills.ai/cli | sh -s -- [install-key]" >&2 echo " curl -fsSL https://lenderskills.ai/cli | sh -s -- kit: [install-key]" >&2 echo " curl -fsSL https://lenderskills.ai/cli | sh -s -- agent: [install-key]" >&2 echo "browse the catalogue at https://lenderskills.ai/, the kits at https://lenderskills.ai/kits/ and the agents at https://lenderskills.ai/agents/" >&2 exit 2 fi TMP="$(mktemp)" trap 'rm -f "$TMP"' EXIT INT TERM # A slug is lowercase letters, digits and hyphens. Anything else is either a # typo or an attempt to reach outside the catalogue, and both end here. valid() { case "$1" in *[!a-z0-9-]*|"") return 1 ;; esac return 0 } # Install one skill. Returns non-zero on failure rather than exiting, so a kit # can report every skill that did not make it instead of stopping at the first. install_one() { SLUG="$1" if ! valid "$SLUG"; then echo "lenderskills: '$SLUG' is not a valid skill slug." >&2 return 2 fi URL="https://lenderskills.ai/i/${SLUG}?raw=1" [ -n "$KEY" ] && URL="${URL}&k=${KEY}" DIR=".claude/skills/${SLUG}" # -f so an HTTP error is a failure rather than a file full of error text. if ! curl -fsSL "$URL" -o "$TMP"; then echo "lenderskills: could not fetch '$SLUG'." >&2 echo " - check the slug at https://lenderskills.ai/skill/${SLUG}/" >&2 echo " - if you passed no key, sign in at https://lenderskills.ai/install to get one" >&2 return 1 fi # The gate document is prose, not a skill file. Recognise it rather than # writing it to disk and leaving someone with a broken skill. if head -n 1 "$TMP" | grep -q '^LENDERSKILLS — THIS INSTALL NEEDS'; then echo "lenderskills: '$SLUG' needs your free install key." >&2 echo " get one at https://lenderskills.ai/install, then run the same command with it on the end." >&2 return 1 fi if [ ! -s "$TMP" ]; then echo "lenderskills: '$SLUG' came back empty. Try https://lenderskills.ai/skill/${SLUG}/." >&2 return 1 fi if [ -f "${DIR}/SKILL.md" ]; then echo "lenderskills: ${DIR}/SKILL.md already exists — overwriting with the current version." fi mkdir -p "$DIR" cat "$TMP" > "${DIR}/SKILL.md" # Errors keep the lowercase "lenderskills:" prefix a unix tool is expected to # use. Success is the one line worth signing properly. echo "LenderSkills — installed ${SLUG} → ${DIR}/SKILL.md" return 0 } case "$ARG" in kit:*) KIT="${ARG#kit:}" if ! valid "$KIT"; then echo "lenderskills: '$KIT' is not a valid kit name. See https://lenderskills.ai/kits/" >&2 exit 2 fi if [ -z "$KEY" ]; then echo "lenderskills: a kit needs your free install key." >&2 echo " get one at https://lenderskills.ai/install, then:" >&2 echo " curl -fsSL https://lenderskills.ai/cli | sh -s -- kit:$KIT " >&2 exit 1 fi LIST="$(mktemp)" trap 'rm -f "$TMP" "$LIST"' EXIT INT TERM if ! curl -fsSL "https://lenderskills.ai/k/${KIT}?list=1" -o "$LIST"; then echo "lenderskills: no starter kit called '$KIT'. See https://lenderskills.ai/kits/" >&2 exit 1 fi OK=0; FAILED="" while IFS= read -r S; do [ -z "$S" ] && continue if install_one "$S"; then OK=$((OK + 1)); else FAILED="$FAILED $S"; fi done < "$LIST" echo echo "LenderSkills — $OK skills installed from the $KIT kit." if [ -n "$FAILED" ]; then echo "lenderskills: these did not install:$FAILED" >&2 echo " retry one with: curl -fsSL https://lenderskills.ai/cli | sh -s -- " >&2 fi echo echo "Next: fill in every [BRACKETED] placeholder with your firm's own values," echo "or ask your assistant to walk you through them across the whole kit." echo "Kit: https://lenderskills.ai/kit/${KIT}/" [ -z "$FAILED" ] || exit 1 ;; agent:*) AGENT="${ARG#agent:}" if ! valid "$AGENT"; then echo "lenderskills: '$AGENT' is not a valid agent name. See https://lenderskills.ai/agents/" >&2 exit 2 fi if [ -z "$KEY" ]; then echo "lenderskills: an agent needs your free install key." >&2 echo " get one at https://lenderskills.ai/install, then:" >&2 echo " curl -fsSL https://lenderskills.ai/cli | sh -s -- agent:$AGENT " >&2 exit 1 fi LIST="$(mktemp)" trap 'rm -f "$TMP" "$LIST"' EXIT INT TERM if ! curl -fsSL "https://lenderskills.ai/a/${AGENT}?list=1" -o "$LIST"; then echo "lenderskills: no agent called '$AGENT'. See https://lenderskills.ai/agents/" >&2 exit 1 fi if ! curl -fsSL "https://lenderskills.ai/a/${AGENT}?raw=1&k=${KEY}" -o "$TMP" || head -n 1 "$TMP" | grep -q '^LENDERSKILLS — THIS INSTALL NEEDS' || [ ! -s "$TMP" ]; then echo "lenderskills: could not fetch the '$AGENT' agent file." >&2 echo " check your key at https://lenderskills.ai/install and try again." >&2 exit 1 fi mkdir -p .claude/agents cat "$TMP" > ".claude/agents/${AGENT}.md" echo "LenderSkills — installed agent ${AGENT} → .claude/agents/${AGENT}.md" OK=0; FAILED="" while IFS= read -r S; do [ -z "$S" ] && continue if install_one "$S"; then OK=$((OK + 1)); else FAILED="$FAILED $S"; fi done < "$LIST" echo echo "LenderSkills — the $AGENT agent and $OK of its skills are installed." if [ -n "$FAILED" ]; then echo "lenderskills: these skills did not install:$FAILED" >&2 echo " the agent runs a reduced version of any skill that is missing." >&2 echo " retry one with: curl -fsSL https://lenderskills.ai/cli | sh -s -- " >&2 fi echo echo "Next: restart Claude Code so it picks up the agent, then fill in every" echo "[BRACKETED] placeholder with your firm's own values." echo "Agent: https://lenderskills.ai/agent/${AGENT}/" [ -z "$FAILED" ] || exit 1 ;; *) install_one "$ARG" || exit $? echo echo "Next: open the file and fill in every [BRACKETED] placeholder with your" echo "firm's own values, or ask your assistant to walk you through them." echo "Listing: https://lenderskills.ai/skill/${ARG}/" ;; esac