-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup
More file actions
executable file
·128 lines (110 loc) · 4.56 KB
/
Copy pathsetup
File metadata and controls
executable file
·128 lines (110 loc) · 4.56 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
#!/usr/bin/env bash
set -euo pipefail
# Symlink GrayCode Skills into an agent's skills directory.
#
# For Rho, prefer `rho skills install GrayCodeAI/graycode-skills <name>`, which
# installs a single skill; this script links every skill (or whole categories)
# from a local checkout.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
VERSION=$(cat "$SCRIPT_DIR/VERSION" 2>/dev/null || echo "unknown")
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
usage() {
echo "Usage: ./setup [OPTIONS]"
echo " --host <agent> Target: rho, claude, cursor, codex (auto-detected)"
echo " --scope <scope> user (default) or project"
echo " --categories <list> Comma-separated categories (default: all)"
echo " --help Show this help"
exit 0
}
HOST=""; SCOPE="user"; CATEGORIES=""
while [[ $# -gt 0 ]]; do
case $1 in
--host) HOST="$2"; shift 2 ;;
--scope) SCOPE="$2"; shift 2 ;;
--categories) CATEGORIES="$2"; shift 2 ;;
--help) usage ;;
*) echo "Unknown: $1"; usage ;;
esac
done
if [[ ! -d "$SCRIPT_DIR/categories" ]]; then
echo -e "${RED}Error: Run from graycode-skills repo root${NC}"; exit 1
fi
if [[ "$SCOPE" != "user" && "$SCOPE" != "project" ]]; then
echo -e "${RED}Unknown scope: $SCOPE (use user or project)${NC}"; exit 1
fi
# Rho's per-user state directory, resolved the way Rho resolves it
# (internal/storage/paths.go): RHO_STATE_DIR, then $XDG_STATE_HOME/rho, then
# the OS config directory + rho/state. `rho skills install` (user scope)
# installs into <state dir>/skills, and Rho loads skills from there.
rho_state_dir() {
if [[ -n "${RHO_STATE_DIR:-}" ]]; then
echo "$RHO_STATE_DIR"
elif [[ -n "${XDG_STATE_HOME:-}" ]]; then
echo "$XDG_STATE_HOME/rho"
elif [[ "$(uname -s)" == "Darwin" ]]; then
echo "$HOME/Library/Application Support/rho/state"
else
echo "${XDG_CONFIG_HOME:-$HOME/.config}/rho/state"
fi
}
detect_host() {
if command -v rho >/dev/null 2>&1 || [[ -d "$(rho_state_dir)" ]]; then
echo "rho"; return
fi
[[ -d "$HOME/.claude" ]] && echo "claude" && return
[[ -d "$HOME/.cursor" ]] && echo "cursor" && return
echo "rho"
}
[[ -z "$HOST" ]] && HOST=$(detect_host) && echo -e "${YELLOW}Auto-detected host: ${HOST}${NC}"
if [[ "$HOST" == "graycode" ]]; then
echo -e "${YELLOW}--host graycode is deprecated; the agent is now Rho. Using --host rho.${NC}"
HOST="rho"
fi
case "$HOST:$SCOPE" in
rho:user) TARGET="$(rho_state_dir)/skills" ;;
rho:project) TARGET=".rho/skills" ;; # loaded only in folders trusted with `rho trust add`
claude:user) TARGET="$HOME/.claude/skills" ;;
claude:project) TARGET=".claude/skills" ;;
cursor:user) TARGET="$HOME/.cursor/plugins" ;;
cursor:project) TARGET=".cursor/plugins" ;;
codex:user) TARGET="$HOME/.codex/plugins" ;;
codex:project) TARGET=".codex/plugins" ;;
*) echo -e "${RED}Unknown host: $HOST${NC}"; exit 1 ;;
esac
# Rho skips symlinked skill directories when it scans for skills, so Rho gets
# copies; the other hosts get symlinks back into this checkout.
MODE="link"
[[ "$HOST" == "rho" ]] && MODE="copy"
mkdir -p "$TARGET"
installed=0; skipped=0
for cat_dir in "$SCRIPT_DIR"/categories/*/; do
[[ -d "$cat_dir" ]] || continue
cat_name=$(basename "$cat_dir")
[[ -n "$CATEGORIES" ]] && [[ ! ",$CATEGORIES," == *",$cat_name,"* ]] && continue
for skill_dir in "$cat_dir"*/; do
[[ -f "$skill_dir/SKILL.md" ]] || continue
skill_name=$(basename "$skill_dir")
target="$TARGET/$skill_name"
if [[ "$MODE" == "link" ]] && [[ -L "$target" ]] && [[ "$(readlink "$target")" == "$skill_dir" ]]; then
skipped=$((skipped + 1)); continue
fi
# Validate skill name contains no path separators to prevent traversal.
if [[ "$skill_name" == *..* ]] || [[ "$skill_name" == */* ]]; then
echo -e "${RED} ✗ Skipping unsafe skill name: $skill_name${NC}"
continue
fi
[[ -e "$target" || -L "$target" ]] && rm -rf "$target"
if [[ "$MODE" == "copy" ]]; then
cp -R "${skill_dir%/}" "$target"
else
ln -s "$skill_dir" "$target"
fi
# Not ((installed++)): under `set -e` that returns 1 when the old
# value is 0 and aborted the script after the first skill.
installed=$((installed + 1))
done
done
echo ""
echo -e "${GREEN}✓ Setup complete${NC}"
echo " Host: $HOST | Target: $TARGET | Mode: $MODE"
echo " Installed: $installed | Skipped: $skipped | Version: $VERSION"