-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·325 lines (282 loc) · 10.7 KB
/
install.sh
File metadata and controls
executable file
·325 lines (282 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
#!/usr/bin/env bash
# ============================================================================
# Universal Claude Code Skill Installer
# ============================================================================
# Usage:
# curl -fsSL https://raw.githubusercontent.com/OWNER/REPO/main/install.sh | bash
# bash install.sh # fresh install or upgrade
# bash install.sh --check-update # check for newer version only
#
# Customize the variables below for your skill repository.
# ============================================================================
set -euo pipefail
# ---------------------------------------------------------------------------
# Skill Configuration (customize these per-repo)
# ---------------------------------------------------------------------------
REPO_OWNER="anombyte93"
REPO_NAME="codify"
SKILL_NAME="codify"
VERSION="1.0.0"
SKILL_DIR="${SKILL_DIR:-${HOME}/.claude/skills/${SKILL_NAME}}"
# ---------------------------------------------------------------------------
# Internal constants
# ---------------------------------------------------------------------------
UPDATES_DIR="${HOME}/.config/claude-skills"
UPDATES_FILE="${UPDATES_DIR}/updates.json"
UPDATE_INTERVAL_SECONDS=86400 # 24 hours
GITHUB_API="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest"
CLONE_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}.git"
# ---------------------------------------------------------------------------
# Colors (disabled when piped or in CI)
# ---------------------------------------------------------------------------
if [[ -t 1 ]] && [[ -z "${CI:-}" ]] && [[ -z "${NO_COLOR:-}" ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
else
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET=''
fi
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
info() { printf "${CYAN}[info]${RESET} %s\n" "$*"; }
ok() { printf "${GREEN}[ok]${RESET} %s\n" "$*"; }
warn() { printf "${YELLOW}[warn]${RESET} %s\n" "$*"; }
err() { printf "${RED}[error]${RESET} %s\n" "$*" >&2; }
die() { err "$@"; exit 1; }
cleanup() {
if [[ -n "${TMPDIR_SKILL:-}" ]] && [[ -d "${TMPDIR_SKILL}" ]]; then
rm -rf "${TMPDIR_SKILL}"
fi
}
trap cleanup EXIT
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}
# ---------------------------------------------------------------------------
# Update check (callable standalone)
# ---------------------------------------------------------------------------
check_update() {
# Respect CI / explicit opt-out
if [[ -n "${CI:-}" ]] || [[ -n "${NO_UPDATE_CHECK:-}" ]]; then
return 0
fi
require_cmd curl
require_cmd date
mkdir -p "${UPDATES_DIR}"
# Throttle: only check once per UPDATE_INTERVAL_SECONDS
if [[ -f "${UPDATES_FILE}" ]]; then
local last_check
last_check=$(python3 -c "
import json, sys
try:
d = json.load(open('${UPDATES_FILE}'))
print(d.get('skills', {}).get('${SKILL_NAME}', {}).get('last_check', 0))
except Exception:
print(0)
" 2>/dev/null || echo 0)
local now
now=$(date +%s)
local elapsed=$(( now - last_check ))
if [[ ${elapsed} -lt ${UPDATE_INTERVAL_SECONDS} ]]; then
# Within cooldown -- read cached result
local cached_latest
cached_latest=$(python3 -c "
import json
try:
d = json.load(open('${UPDATES_FILE}'))
print(d.get('skills', {}).get('${SKILL_NAME}', {}).get('latest', ''))
except Exception:
print('')
" 2>/dev/null || echo "")
if [[ -n "${cached_latest}" ]] && [[ "${cached_latest}" != "${VERSION}" ]]; then
warn "Update available: ${VERSION} -> ${cached_latest}"
info "Run: curl -fsSL https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/main/install.sh | bash"
fi
return 0
fi
fi
# Fetch latest release from GitHub API
local api_response
api_response=$(curl -fsSL --max-time 5 \
-H "Accept: application/vnd.github+json" \
"${GITHUB_API}" 2>/dev/null) || {
# Network failure is non-fatal for update checks
return 0
}
local latest_version
latest_version=$(printf '%s' "${api_response}" | python3 -c "
import json, sys
try:
data = json.load(sys.stdin)
tag = data.get('tag_name', '')
# Strip leading 'v' if present
print(tag.lstrip('v'))
except Exception:
print('')
" 2>/dev/null || echo "")
if [[ -z "${latest_version}" ]]; then
return 0
fi
# Write cache
local now
now=$(date +%s)
python3 -c "
import json, os
path = '${UPDATES_FILE}'
try:
data = json.load(open(path))
except Exception:
data = {}
data.setdefault('skills', {})
data['skills']['${SKILL_NAME}'] = {
'last_check': ${now},
'latest': '${latest_version}',
'current': '${VERSION}'
}
with open(path, 'w') as f:
json.dump(data, f, indent=2)
" 2>/dev/null || true
if [[ "${latest_version}" != "${VERSION}" ]]; then
warn "Update available: ${VERSION} -> ${latest_version}"
info "Run: curl -fsSL https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/main/install.sh | bash"
else
ok "You are on the latest version (${VERSION})"
fi
}
# ---------------------------------------------------------------------------
# Install logic
# ---------------------------------------------------------------------------
install_skill() {
local mode="install"
info "Claude Code Skill Installer"
info "Skill: ${BOLD}${SKILL_NAME}${RESET} v${VERSION}"
printf "\n"
require_cmd git
# ------------------------------------------------------------------
# Detect upgrade vs fresh install
# ------------------------------------------------------------------
if [[ -d "${SKILL_DIR}" ]]; then
mode="upgrade"
local existing_version="unknown"
if [[ -f "${SKILL_DIR}/.version" ]]; then
existing_version=$(head -1 "${SKILL_DIR}/.version" 2>/dev/null || echo "unknown")
fi
info "Existing installation detected (${existing_version})"
info "Mode: upgrade"
# Back up SKILL.md if it exists
if [[ -f "${SKILL_DIR}/SKILL.md" ]]; then
cp "${SKILL_DIR}/SKILL.md" "${SKILL_DIR}/SKILL.md.bak"
ok "Backed up SKILL.md -> SKILL.md.bak"
fi
else
info "Mode: fresh install"
fi
# ------------------------------------------------------------------
# Clone repo to temp directory
# ------------------------------------------------------------------
TMPDIR_SKILL=$(mktemp -d "${TMPDIR:-/tmp}/claude-skill-XXXXXX")
info "Cloning ${REPO_OWNER}/${REPO_NAME}..."
git clone --depth 1 --quiet "${CLONE_URL}" "${TMPDIR_SKILL}/repo" 2>/dev/null \
|| die "Failed to clone repository. Check REPO_OWNER/REPO_NAME and network."
# ------------------------------------------------------------------
# Locate skill source within the repo
# ------------------------------------------------------------------
# Convention: skill files live at repo root OR under a directory named
# after the skill. We check both.
local src_dir="${TMPDIR_SKILL}/repo"
if [[ -d "${TMPDIR_SKILL}/repo/${SKILL_NAME}" ]]; then
src_dir="${TMPDIR_SKILL}/repo/${SKILL_NAME}"
fi
# Verify at minimum SKILL.md exists
if [[ ! -f "${src_dir}/SKILL.md" ]]; then
die "SKILL.md not found in repository (checked ${src_dir}). Cannot install."
fi
# ------------------------------------------------------------------
# Copy skill files to SKILL_DIR
# ------------------------------------------------------------------
mkdir -p "${SKILL_DIR}"
# Core file: SKILL.md (always required)
cp "${src_dir}/SKILL.md" "${SKILL_DIR}/SKILL.md"
ok "Installed SKILL.md"
# Optional: script.py (or any .py entrypoint)
if [[ -f "${src_dir}/script.py" ]]; then
cp "${src_dir}/script.py" "${SKILL_DIR}/script.py"
ok "Installed script.py"
fi
# Optional: templates/ directory
if [[ -d "${src_dir}/templates" ]]; then
rm -rf "${SKILL_DIR}/templates"
cp -r "${src_dir}/templates" "${SKILL_DIR}/templates"
ok "Installed templates/"
fi
# Optional: reference/ directory
if [[ -d "${src_dir}/reference" ]]; then
rm -rf "${SKILL_DIR}/reference"
cp -r "${src_dir}/reference" "${SKILL_DIR}/reference"
ok "Installed reference/"
fi
# Optional: install.sh itself (so the skill is self-updating)
if [[ -f "${src_dir}/install.sh" ]]; then
cp "${src_dir}/install.sh" "${SKILL_DIR}/install.sh"
chmod +x "${SKILL_DIR}/install.sh"
fi
# ------------------------------------------------------------------
# Write .version file
# ------------------------------------------------------------------
local timestamp
timestamp=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
cat > "${SKILL_DIR}/.version" <<VEOF
${VERSION}
installed: ${timestamp}
mode: ${mode}
repo: ${REPO_OWNER}/${REPO_NAME}
VEOF
ok "Wrote .version (${VERSION}, ${timestamp})"
# ------------------------------------------------------------------
# Success
# ------------------------------------------------------------------
printf "\n"
printf "${GREEN}${BOLD}Successfully %s ${SKILL_NAME} v${VERSION}${RESET}\n" \
"$([ "${mode}" = "upgrade" ] && echo "upgraded" || echo "installed")"
printf " Location: %s\n" "${SKILL_DIR}"
if [[ "${mode}" = "upgrade" ]] && [[ -f "${SKILL_DIR}/SKILL.md.bak" ]]; then
printf " Backup: %s\n" "${SKILL_DIR}/SKILL.md.bak"
fi
printf "\n"
info "To use this skill in Claude Code, reference it with:"
printf " ${CYAN}/%s${RESET}\n" "${SKILL_NAME}"
printf "\n"
}
# ---------------------------------------------------------------------------
# Entrypoint
# ---------------------------------------------------------------------------
main() {
case "${1:-}" in
--check-update|-u)
check_update
;;
--version|-v)
echo "${SKILL_NAME} v${VERSION}"
;;
--help|-h)
printf "Usage: %s [--check-update | --version | --help]\n" "${0##*/}"
printf "\n"
printf " (no args) Install or upgrade the skill\n"
printf " --check-update Check GitHub for a newer release\n"
printf " --version Print current version\n"
printf " --help Show this help\n"
;;
"")
install_skill
check_update
;;
*)
die "Unknown argument: $1 (try --help)"
;;
esac
}
main "$@"