Skip to content

Commit e7c2216

Browse files
JasonLoclaude
andcommitted
install: add bootstrap installer and wire pinned URLs into release flow
Adds `scripts/install.sh` and `scripts/uninstall.sh` as a curl-pipeable bootstrap for skill-sommelier — checks for Claude Code (offering to install via the native installer with an npm fallback), prompts for optional `gh` and `uv`, then prints the two `/plugin` commands users paste into Claude Code. Honors `--yes` / `INSTALLER_ASSUME_YES=1` and auto-detects non-TTY for CI. CLAUDE.md gets a narrow carve-out documenting that these two files are distribution infrastructure, not capabilities — top-level scripts are still otherwise banned. README gains pinned and `main` curl one-liners plus an unattended example. `ssm-repo-release` now rewrites the `JasonLo/skill-sommelier/v<TAG>` URLs across README and both scripts on each release, and stages by `git diff --name-only` instead of a hard-coded file list so older releases that predate the installer still work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2f67305 commit e7c2216

5 files changed

Lines changed: 338 additions & 8 deletions

File tree

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ Guidance for Claude Code when working in this repo.
88

99
There is no build system, test suite, or application code — just `SKILL.md` files with YAML frontmatter and procedural instructions. **No standalone scripts.** All capabilities ship as skills; supporting scripts inside a skill directory (`scripts/`) are fine, top-level scripts are not.
1010

11+
Narrow exception: `scripts/install.sh` and `scripts/uninstall.sh` at the repo root are distribution infrastructure, not capabilities — curl-pipeable installers need stable repo-root URLs. Don't add other top-level scripts under this exception; new capabilities still go in `skills/` or `maintainer-skills/`.
12+
1113
```
1214
skills/ # public — distributed
1315
maintainer-skills/ # local-only — repo upkeep, NOT distributed
1416
.claude/skills/ # symlink farm so project-level discovery picks up both
17+
scripts/ # carve-out: install.sh / uninstall.sh only
1518
```
1619

1720
## Public vs maintainer-only

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,29 @@ Everything is `SKILL.md` files — no build system, no scripts.
66

77
## Install
88

9+
Inside Claude Code:
10+
911
```
1012
/plugin marketplace add JasonLo/skill-sommelier
1113
/plugin install skill-sommelier@skill-sommelier
1214
```
1315

14-
Skills are namespaced as `/skill-sommelier:<name>`. Update with `/plugin marketplace update`.
16+
Don't have Claude Code yet, or want a one-liner that also sets up companions like `gh` and `uv`?
17+
18+
```bash
19+
# Pinned (recommended)
20+
curl -LsSf https://raw.githubusercontent.com/JasonLo/skill-sommelier/v0.6.0/scripts/install.sh | sh
21+
22+
# Tracking main (contributors)
23+
curl -LsSf https://raw.githubusercontent.com/JasonLo/skill-sommelier/main/scripts/install.sh | sh
24+
25+
# Unattended (CI / Docker / devcontainers)
26+
curl -LsSf https://raw.githubusercontent.com/JasonLo/skill-sommelier/v0.6.0/scripts/install.sh | sh -s -- --yes
27+
```
28+
29+
The bootstrap installer checks for Claude Code (offering to install it if missing), optionally installs `gh` and `uv`, then prints the two `/plugin` commands above for you to paste into Claude Code. The plugin install itself happens inside Claude Code — that's the only way to add a plugin.
30+
31+
Skills are namespaced as `/skill-sommelier:<name>`. Update with `/skill-sommelier:ss-update` or `/plugin marketplace update`. Uninstall via `/plugin uninstall skill-sommelier@skill-sommelier` (or [`scripts/uninstall.sh`](scripts/uninstall.sh) for a guided cleanup).
1532

1633
## Quickstart
1734

maintainer-skills/ssm-repo-release/SKILL.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,30 +98,40 @@ Highlight the suggested option. Ask the user to choose.
9898

9999
1. Edit `.claude-plugin/plugin.json` — update the `"version"` field
100100
2. Edit `.claude-plugin/marketplace.json` — update the `"version"` field inside `metadata`
101-
3. Read both files back to confirm the version is correct
101+
3. Bump the pinned installer URLs in `README.md`, `scripts/install.sh`, and `scripts/uninstall.sh`. One sed handles all of them — they all match `JasonLo/skill-sommelier/v<old>/scripts/`:
102+
```bash
103+
# NEW_VERSION already has no leading "v", e.g. 0.7.0
104+
git ls-files README.md scripts/install.sh scripts/uninstall.sh | \
105+
xargs sed -i -E "s|(JasonLo/skill-sommelier/)v[0-9]+\.[0-9]+\.[0-9]+(/scripts/)|\1v${NEW_VERSION}\2|g"
106+
```
107+
Then verify with `grep -n "JasonLo/skill-sommelier/v" README.md scripts/install.sh scripts/uninstall.sh` — every line should show the new tag.
108+
109+
If `scripts/` doesn't exist in this repo, skip step 3 (older releases predate the bootstrap installer).
110+
111+
4. Read all bumped files back to confirm the version is correct.
102112

103-
**Exit:** Both files show the new version.
113+
**Exit:** All files show the new version.
104114

105115
## Phase 5 — Commit, Tag, Push
106116

107-
**Entry:** Version bumped in both files.
117+
**Entry:** Version bumped in all relevant files.
108118

109-
**⚠ SAFETY GATE:** Before proceeding, display this summary and wait for explicit confirmation:
119+
**⚠ SAFETY GATE:** Before proceeding, display this summary and wait for explicit confirmation. Use `git diff --stat` to list the files that actually changed — don't hard-code the list, since older releases may not have the bootstrap installer:
110120

111121
```
112122
Ready to release:
113123
Version: v<NEW_VERSION>
114-
Files: plugin.json, marketplace.json
124+
Files: <list from git diff --stat>
115125
Action: commit → tag → push (triggers GitHub release workflow)
116126
117127
Proceed? (yes/no)
118128
```
119129

120130
Only continue after user confirms.
121131

122-
1. Stage the two changed files:
132+
1. Stage every file that was actually changed in Phase 4 (use `git diff --name-only` rather than a hard-coded list):
123133
```
124-
git add .claude-plugin/plugin.json .claude-plugin/marketplace.json
134+
git add $(git diff --name-only)
125135
```
126136

127137
2. Commit with a release message:

scripts/install.sh

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#!/bin/sh
2+
# skill-sommelier bootstrap installer.
3+
#
4+
# Prepares the environment for the skill-sommelier Claude Code plugin:
5+
# 1. Claude Code (required) — installs if missing
6+
# 2. gh (optional companion) — used by ss-skill-discover
7+
# 3. uv (optional companion) — used by Python-flavored skills
8+
# 4. Prints the two slash commands you paste into Claude Code to add
9+
# the marketplace and install the plugin.
10+
#
11+
# Plugin install itself happens *inside* Claude Code — there is no shell
12+
# entry point for `/plugin marketplace add`. This script just guarantees
13+
# the prerequisites are in place.
14+
#
15+
# Usage:
16+
# curl -LsSf https://raw.githubusercontent.com/JasonLo/skill-sommelier/v0.6.0/scripts/install.sh | sh
17+
#
18+
# Flags / env:
19+
# --yes, INSTALLER_ASSUME_YES=1 non-interactive; required deps Y, optional N
20+
# INSTALL_CLAUDE_CODE=0 never auto-install Claude Code
21+
# INSTALL_GH=1 opt in to gh during unattended runs
22+
# INSTALL_UV=1 opt in to uv during unattended runs
23+
24+
set -eu
25+
26+
ASSUME_YES="${INSTALLER_ASSUME_YES:-0}"
27+
INSTALL_CLAUDE_CODE_OVERRIDE="${INSTALL_CLAUDE_CODE:-}"
28+
INSTALL_GH_OVERRIDE="${INSTALL_GH:-}"
29+
INSTALL_UV_OVERRIDE="${INSTALL_UV:-}"
30+
31+
while [ $# -gt 0 ]; do
32+
case "$1" in
33+
-y|--yes) ASSUME_YES=1; shift ;;
34+
-h|--help)
35+
sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'
36+
exit 0 ;;
37+
*) printf 'Unknown flag: %s\n' "$1" >&2; exit 1 ;;
38+
esac
39+
done
40+
41+
# Pipe-from-curl into a non-TTY shell (CI, Docker RUN, devcontainers) → unattended.
42+
if [ ! -t 0 ] && [ ! -r /dev/tty ]; then
43+
ASSUME_YES=1
44+
fi
45+
46+
# ---------------------------------------------------------------------------
47+
# helpers
48+
# ---------------------------------------------------------------------------
49+
50+
confirm() {
51+
# confirm "<prompt>" <default Y|N>
52+
prompt="$1"
53+
default="${2:-N}"
54+
if [ "$ASSUME_YES" = "1" ]; then
55+
[ "$default" = "Y" ]
56+
return
57+
fi
58+
printf '%s ' "$prompt"
59+
read -r reply < /dev/tty || reply=""
60+
case "$reply" in
61+
[Yy]) return 0 ;;
62+
[Nn]) return 1 ;;
63+
"") [ "$default" = "Y" ] ;;
64+
*) return 1 ;;
65+
esac
66+
}
67+
68+
have() { command -v "$1" >/dev/null 2>&1; }
69+
70+
# Detect a usable system package manager for installing gh. Empty if none.
71+
detect_pkg_mgr() {
72+
if have brew; then echo brew
73+
elif have apt-get;then echo apt
74+
elif have dnf; then echo dnf
75+
elif have yum; then echo yum
76+
elif have pacman; then echo pacman
77+
elif have zypper; then echo zypper
78+
fi
79+
}
80+
81+
# Download a remote install script to a tmp file, then execute. Never pipe
82+
# third-party URLs straight to a shell — the file goes to disk first so it
83+
# could be reviewed if anything looks off.
84+
run_remote_installer() {
85+
url="$1"
86+
label="$2"
87+
runner="${3:-sh}"
88+
tmp="$(mktemp "${TMPDIR:-/tmp}/skill-sommelier-${label}.XXXXXX.sh")"
89+
trap 'rm -f "$tmp"' EXIT HUP INT TERM
90+
if ! curl -fsSL "$url" -o "$tmp"; then
91+
printf ' ✗ failed to download %s installer from %s\n' "$label" "$url" >&2
92+
rm -f "$tmp"
93+
trap - EXIT HUP INT TERM
94+
return 1
95+
fi
96+
"$runner" "$tmp"
97+
rc=$?
98+
rm -f "$tmp"
99+
trap - EXIT HUP INT TERM
100+
return $rc
101+
}
102+
103+
echo "=== skill-sommelier bootstrap ==="
104+
echo
105+
106+
# ---------------------------------------------------------------------------
107+
# [1/4] Claude Code (required)
108+
# ---------------------------------------------------------------------------
109+
110+
printf '[1/4] Checking Claude Code... '
111+
if have claude; then
112+
cc_version="$(claude --version 2>/dev/null | head -n1 || echo 'installed')"
113+
printf '✓ %s\n' "$cc_version"
114+
else
115+
echo "not found"
116+
if [ "$INSTALL_CLAUDE_CODE_OVERRIDE" = "0" ]; then
117+
echo " INSTALL_CLAUDE_CODE=0 set — skipping. Install Claude Code yourself, then re-run."
118+
exit 1
119+
fi
120+
if confirm " Install Claude Code? (Y/n)" "Y"; then
121+
installed=0
122+
# Anthropic's native installer (preferred — no Node required).
123+
if run_remote_installer "https://claude.ai/install.sh" "claude-code" "bash"; then
124+
installed=1
125+
elif have npm; then
126+
echo " native installer failed — falling back to npm"
127+
if npm install -g @anthropic-ai/claude-code; then
128+
installed=1
129+
fi
130+
fi
131+
if [ "$installed" = "0" ]; then
132+
echo " ✗ Could not install Claude Code automatically." >&2
133+
echo " See https://docs.claude.com/claude-code for manual instructions." >&2
134+
exit 1
135+
fi
136+
if ! have claude; then
137+
echo " ⚠ 'claude' is installed but not on PATH yet."
138+
echo " Open a new shell, or follow the installer's PATH hint above."
139+
fi
140+
else
141+
echo " Skipped. skill-sommelier requires Claude Code — install it and re-run."
142+
exit 1
143+
fi
144+
fi
145+
echo
146+
147+
# ---------------------------------------------------------------------------
148+
# [2/4] gh — optional, used by ss-skill-discover for GitHub search
149+
# ---------------------------------------------------------------------------
150+
151+
printf '[2/4] Optional: gh CLI (for ss-skill-discover)... '
152+
if have gh; then
153+
printf '✓ %s\n' "$(gh --version 2>/dev/null | head -n1)"
154+
else
155+
echo "not found"
156+
default_gh="N"
157+
[ "$INSTALL_GH_OVERRIDE" = "1" ] && default_gh="Y"
158+
if confirm " Install gh? (y/N)" "$default_gh"; then
159+
pm="$(detect_pkg_mgr)"
160+
case "$pm" in
161+
brew) brew install gh ;;
162+
apt) sudo -v && sudo apt-get update && sudo apt-get install -y gh ;;
163+
dnf) sudo -v && sudo dnf install -y gh ;;
164+
yum) sudo -v && sudo yum install -y gh ;;
165+
pacman) sudo -v && sudo pacman -S --noconfirm github-cli ;;
166+
zypper) sudo -v && sudo zypper install -y gh ;;
167+
*)
168+
echo " ⚠ No supported package manager detected."
169+
echo " See https://github.com/cli/cli#installation"
170+
;;
171+
esac
172+
have gh && printf ' ✓ gh installed\n' || true
173+
else
174+
echo " Skipped. ss-skill-discover will prompt you for gh later if it needs it."
175+
fi
176+
fi
177+
echo
178+
179+
# ---------------------------------------------------------------------------
180+
# [3/4] uv — optional, used by Python skills (ss-modern-python, ss-python-to-chtc, ...)
181+
# ---------------------------------------------------------------------------
182+
183+
printf '[3/4] Optional: uv (for Python skills)... '
184+
if have uv; then
185+
printf '✓ %s\n' "$(uv --version 2>/dev/null | head -n1)"
186+
else
187+
echo "not found"
188+
default_uv="N"
189+
[ "$INSTALL_UV_OVERRIDE" = "1" ] && default_uv="Y"
190+
if confirm " Install uv? (y/N)" "$default_uv"; then
191+
if run_remote_installer "https://astral.sh/uv/install.sh" "uv" "sh"; then
192+
have uv && printf ' ✓ uv installed\n' || \
193+
echo " ⚠ uv installed but not on PATH yet — open a new shell."
194+
else
195+
echo " ✗ uv install failed. See https://docs.astral.sh/uv/" >&2
196+
fi
197+
else
198+
echo " Skipped. The Python-flavored skills will work without it, but slower."
199+
fi
200+
fi
201+
echo
202+
203+
# ---------------------------------------------------------------------------
204+
# [4/4] Plugin install — runs inside Claude Code
205+
# ---------------------------------------------------------------------------
206+
207+
cat <<'EOF'
208+
[4/4] Bootstrap complete.
209+
210+
Open Claude Code and paste these two commands:
211+
212+
/plugin marketplace add JasonLo/skill-sommelier
213+
/plugin install skill-sommelier@skill-sommelier
214+
215+
Quickstart once installed:
216+
217+
/skill-sommelier:ss-skill-discover
218+
219+
Updates:
220+
/skill-sommelier:ss-update — check + apply skill-sommelier updates
221+
/plugin marketplace update — refresh the marketplace cache
222+
223+
Uninstall later:
224+
curl -LsSf https://raw.githubusercontent.com/JasonLo/skill-sommelier/v0.6.0/scripts/uninstall.sh | sh
225+
EOF

scripts/uninstall.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/bin/sh
2+
# skill-sommelier uninstaller.
3+
#
4+
# Removes whatever the bootstrap installer touched outside of Claude Code.
5+
# The plugin itself lives inside Claude Code's marketplace cache and can only
6+
# be removed via slash commands — this script prints those for you.
7+
#
8+
# Usage:
9+
# curl -LsSf https://raw.githubusercontent.com/JasonLo/skill-sommelier/v0.6.0/scripts/uninstall.sh | sh
10+
#
11+
# Flags:
12+
# --yes skip confirmation prompts (also auto-enabled when not on a TTY)
13+
14+
set -eu
15+
16+
ASSUME_YES="${INSTALLER_ASSUME_YES:-0}"
17+
18+
while [ $# -gt 0 ]; do
19+
case "$1" in
20+
-y|--yes) ASSUME_YES=1; shift ;;
21+
-h|--help)
22+
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
23+
exit 0 ;;
24+
*) printf 'Unknown flag: %s\n' "$1" >&2; exit 1 ;;
25+
esac
26+
done
27+
28+
if [ ! -t 0 ] && [ ! -r /dev/tty ]; then
29+
ASSUME_YES=1
30+
fi
31+
32+
confirm() {
33+
[ "$ASSUME_YES" = "1" ] && return 0
34+
printf '%s (y/N): ' "$1"
35+
read -r reply < /dev/tty || reply=""
36+
case "$reply" in [Yy]) return 0 ;; *) return 1 ;; esac
37+
}
38+
39+
echo "=== skill-sommelier uninstaller ==="
40+
echo
41+
42+
cat <<'EOF'
43+
The skill-sommelier plugin is managed by Claude Code, not by this script.
44+
To remove it, open Claude Code and run:
45+
46+
/plugin uninstall skill-sommelier@skill-sommelier
47+
/plugin marketplace remove JasonLo/skill-sommelier
48+
49+
EOF
50+
51+
# Optional cleanup of skill-sommelier's own profile cache (built by ss-skill-discover).
52+
PROFILE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/skill-sommelier"
53+
if [ -d "$PROFILE_DIR" ]; then
54+
if confirm "Delete locally cached profile at $PROFILE_DIR?"; then
55+
rm -rf "$PROFILE_DIR"
56+
printf ' ✓ removed %s\n' "$PROFILE_DIR"
57+
else
58+
printf ' Kept %s\n' "$PROFILE_DIR"
59+
fi
60+
fi
61+
62+
# The bootstrap installer offered Claude Code, gh, and uv. Removing those is
63+
# out of scope for this uninstaller — they're general-purpose tools you may
64+
# use elsewhere. To remove them yourself:
65+
cat <<'EOF'
66+
67+
The bootstrap installer also offered Claude Code, gh, and uv. Those are
68+
left in place — remove them yourself if you no longer need them:
69+
70+
Claude Code: see https://docs.claude.com/claude-code
71+
gh: your package manager (e.g. `brew uninstall gh`)
72+
uv: uv self uninstall (or `rm ~/.cargo/bin/uv` on older installs)
73+
74+
Done.
75+
EOF

0 commit comments

Comments
 (0)