-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·85 lines (74 loc) · 2.97 KB
/
install.sh
File metadata and controls
executable file
·85 lines (74 loc) · 2.97 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
#!/usr/bin/env bash
# Fallback installer for Claude Code versions without plugin marketplace support.
# Preferred install:
# /plugin marketplace add harnessprotocol/harness-kit
# /plugin install research@harness-kit
set -euo pipefail
REPO="harnessprotocol/harness-kit"
BRANCH="main"
RAW_BASE="https://raw.githubusercontent.com/${REPO}/${BRANCH}"
SKILLS_DEST="${HOME}/.claude/skills"
# Detect local vs remote mode.
# When piped through bash (curl | bash), BASH_SOURCE[0] is unset or "-".
SCRIPT_DIR=""
if [[ -n "${BASH_SOURCE[0]:-}" && "${BASH_SOURCE[0]}" != "-" ]]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
fi
LOCAL_PLUGINS=""
if [[ -n "$SCRIPT_DIR" && -d "${SCRIPT_DIR}/plugins" ]]; then
LOCAL_PLUGINS="${SCRIPT_DIR}/plugins"
fi
if [[ -n "$LOCAL_PLUGINS" ]]; then
echo "Local install: copying skills from ${LOCAL_PLUGINS}/"
for plugin_dir in "${LOCAL_PLUGINS}"/*/; do
plugin=$(basename "$plugin_dir")
skills_src="${plugin_dir}skills"
if [[ -d "$skills_src" ]]; then
mkdir -p "$SKILLS_DEST"
cp -r "${skills_src}/"* "$SKILLS_DEST/"
echo " ~/.claude/skills/ (from ${plugin})"
fi
done
else
echo "Remote install: downloading from ${REPO}"
MARKETPLACE_JSON=$(curl -fsSL "${RAW_BASE}/.claude-plugin/marketplace.json")
mapfile -t PLUGINS < <(python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
print('\n'.join(p['name'] for p in data['plugins']))
" <<< "$MARKETPLACE_JSON")
# Fetch skill listing for each plugin.
# Most plugins have a single skill matching the plugin name.
# Multi-skill plugins (e.g. harness-share) list skills in plugin.json — we discover
# them dynamically so every skill is installed, not just the first one.
for plugin in "${PLUGINS[@]}"; do
plugin_json_url="${RAW_BASE}/plugins/${plugin}/.claude-plugin/plugin.json"
plugin_json=$(curl -fsSL "$plugin_json_url" 2>/dev/null || echo "{}")
skill_names=$(PLUGIN_NAME="$plugin" python3 -c "
import json, sys, os
data = json.loads(sys.stdin.read())
skills = data.get('skills', [])
print('\n'.join(s['name'] for s in skills) if skills else os.environ['PLUGIN_NAME'])
" <<< "$plugin_json")
while IFS= read -r skill; do
# Reject names that could traverse the filesystem
if ! [[ "$skill" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo " [warn] Skipping invalid skill name: ${skill}" >&2
continue
fi
dest="${SKILLS_DEST}/${skill}"
mkdir -p "$dest"
skill_url="${RAW_BASE}/plugins/${plugin}/skills/${skill}/SKILL.md"
if curl -fsSL "$skill_url" -o "${dest}/SKILL.md"; then
curl -fsSL "${RAW_BASE}/plugins/${plugin}/skills/${skill}/README.md" -o "${dest}/README.md" 2>/dev/null || true
echo " ~/.claude/skills/${skill}/"
else
echo " [warn] Could not download ${plugin}/${skill} — skipping" >&2
rmdir "$dest" 2>/dev/null || true
fi
done <<< "$skill_names"
done
echo "Installed all skills."
fi
echo ""
echo "Done. Restart Claude Code for skills to take effect."