-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinstall.sh.tmpl
More file actions
256 lines (234 loc) · 8.3 KB
/
Copy pathinstall.sh.tmpl
File metadata and controls
256 lines (234 loc) · 8.3 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
#!/bin/sh
# Frontend Checklist Skill installer.
# Usage:
# curl -fsSL https://github.com/wsafight/front-end-checklist/releases/latest/download/install.sh | sh -s -- <tool> [--local] [--uninstall]
# Tools: claude | kiro | cursor | codex
# Defaults: claude, global install.
set -eu
VERSION="__VERSION__"
REPO="wsafight/front-end-checklist"
ASSET_TARBALL="frontend-checklist-skill.tar.gz"
ASSET_CURSOR="frontend-checklist-cursor.mdc"
ASSET_SUMS="sha256sums.txt"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
TOOL="claude"
SCOPE="global"
ACTION="install"
FORCE=0
for arg in "$@"; do
case "$arg" in
claude|kiro|cursor|codex) TOOL="$arg" ;;
--local) SCOPE="local" ;;
--global) SCOPE="global" ;;
--uninstall) ACTION="uninstall" ;;
--force|-f) FORCE=1 ;;
-h|--help)
cat <<'EOF'
Frontend Checklist Skill installer.
Usage:
curl -fsSL https://github.com/wsafight/front-end-checklist/releases/latest/download/install.sh | sh -s -- <tool> [--local] [--uninstall] [--force]
Tools: claude | kiro | cursor | codex
Default: claude, global install. --force reinstalls even if same version is present.
EOF
exit 0 ;;
*) echo "unknown arg: $arg" >&2; exit 2 ;;
esac
done
say() { printf '%s\n' "$*"; }
err() { printf 'error: %s\n' "$*" >&2; }
need() { command -v "$1" >/dev/null 2>&1 || { err "missing required command: $1"; exit 1; }; }
need curl
need tar
command -v shasum >/dev/null 2>&1 || command -v sha256sum >/dev/null 2>&1 || { err "missing shasum or sha256sum"; exit 1; }
sha256() {
if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'
else sha256sum "$1" | awk '{print $1}'
fi
}
# Resolve target dir per tool + scope.
resolve_dir() {
case "$TOOL" in
claude) base=".claude/skills" ;;
kiro) base=".kiro/skills" ;;
cursor) base=".cursor/rules" ;;
codex) base=".frontend-checklist" ;;
*) err "unknown tool: $TOOL"; exit 2 ;;
esac
if [ "$SCOPE" = "global" ]; then printf '%s/%s\n' "$HOME" "$base"
else printf '%s/%s\n' "$PWD" "$base"
fi
}
TARGET_DIR="$(resolve_dir)"
TMP="$(mktemp -d 2>/dev/null || mktemp -d -t fcs)"
BACKUP=""
BACKUP_TARGET=""
cleanup() {
status=$?
[ -n "$TMP" ] && rm -rf "$TMP"
if [ $status -ne 0 ]; then
# Failure path: drop half-written target, then restore backup if any.
[ -n "$BACKUP_TARGET" ] && rm -rf "$BACKUP_TARGET" 2>/dev/null || true
if [ -n "$BACKUP" ] && [ -e "$BACKUP" ]; then
mv "$BACKUP" "$BACKUP_TARGET"
err "rolled back to previous version"
fi
else
# Success path: discard stale backup.
[ -n "$BACKUP" ] && [ -e "$BACKUP" ] && rm -rf "$BACKUP"
fi
exit $status
}
trap cleanup EXIT INT TERM
ensure_sums() {
[ -f "$TMP/sums" ] && return
curl -fsSL --retry 3 --retry-delay 1 "${BASE_URL}/${ASSET_SUMS}" -o "$TMP/sums"
}
fetch_verified() {
asset="$1"; dest="$2"
curl -fsSL --retry 3 --retry-delay 1 "${BASE_URL}/${asset}" -o "$dest"
ensure_sums
expected=$(awk -v asset="$asset" '$2 == asset || $2 == "*" asset { print $1; exit }' "$TMP/sums")
[ -n "$expected" ] || { err "checksum not found for $asset"; return 1; }
actual=$(sha256 "$dest")
if [ "$expected" != "$actual" ]; then
err "checksum mismatch for $asset"
err " expected: $expected"
err " actual: $actual"
return 1
fi
}
# Download tarball, back up existing install, extract, verify required files.
install_tarball() {
INSTALL_PATH="$TARGET_DIR/frontend-checklist"
BACKUP_TARGET="$INSTALL_PATH"
mkdir -p "$TARGET_DIR"
if [ -d "$INSTALL_PATH" ]; then
BACKUP="${INSTALL_PATH}.bak.$(date +%s)"
mv "$INSTALL_PATH" "$BACKUP"
fi
fetch_verified "$ASSET_TARBALL" "$TMP/skill.tar.gz"
mkdir -p "$INSTALL_PATH"
tar -xzf "$TMP/skill.tar.gz" -C "$INSTALL_PATH"
[ -f "$INSTALL_PATH/SKILL.md" ] || { err "archive missing SKILL.md"; exit 1; }
for extra in "$@"; do
[ -f "$INSTALL_PATH/$extra" ] || { err "archive missing $extra"; exit 1; }
done
printf '%s\n' "$VERSION" > "$INSTALL_PATH/.version"
}
# Skip reinstall if target already has the same version. Cursor stores version in a
# sibling .version file since the rule itself is a single .mdc.
is_same_version() {
[ "$FORCE" = "1" ] && return 1
marker="$1"
shift
[ -f "$marker" ] || return 1
[ "$(cat "$marker" 2>/dev/null)" = "$VERSION" ] || return 1
for required in "$@"; do
[ -f "$required" ] || return 1
done
return 0
}
update_codex_agents() {
agents_file="${AGENTS_FILE:-$PWD/AGENTS.md}"
[ "$SCOPE" = "global" ] && agents_file="$HOME/.codex/AGENTS.md" && mkdir -p "$HOME/.codex"
touch "$agents_file"
# Strip any existing block, then append fresh one.
tmpf="$TMP/agents.md"
awk '
/<!-- BEGIN frontend-checklist -->/ { skip=1; next }
/<!-- END frontend-checklist -->/ { skip=0; next }
!skip { print }
' "$agents_file" > "$tmpf"
{
cat "$tmpf"
printf '\n<!-- BEGIN frontend-checklist -->\n'
printf '@%s/SKILL.md\n' "$INSTALL_PATH"
printf '<!-- END frontend-checklist -->\n'
} > "$agents_file"
say "✓ Codex: updated $agents_file"
}
# --- Codex path: install full skill and merge/remove a marked block in AGENTS.md ---
codex_install() {
INSTALL_PATH="$TARGET_DIR/frontend-checklist"
if is_same_version "$INSTALL_PATH/.version" "$INSTALL_PATH/SKILL.md" "$INSTALL_PATH/references/checklist.md"; then
update_codex_agents
say "✓ Codex: already at $VERSION, skipping archive reinstall (use --force to reinstall)"
return
fi
install_tarball "references/checklist.md"
update_codex_agents
}
codex_uninstall() {
agents_file="${AGENTS_FILE:-$PWD/AGENTS.md}"
[ "$SCOPE" = "global" ] && agents_file="$HOME/.codex/AGENTS.md"
if [ -f "$agents_file" ]; then
tmpf="$TMP/agents.md"
awk '
/<!-- BEGIN frontend-checklist -->/ { skip=1; next }
/<!-- END frontend-checklist -->/ { skip=0; next }
!skip { print }
' "$agents_file" > "$tmpf"
mv "$tmpf" "$agents_file"
fi
rm -rf "$TARGET_DIR/frontend-checklist"
rmdir "$TARGET_DIR" 2>/dev/null || true
say "✓ Codex: removed marker block and $TARGET_DIR/frontend-checklist"
}
# --- Cursor path: single .mdc file ---
cursor_install() {
version_file="$TARGET_DIR/frontend-checklist.mdc.version"
if is_same_version "$version_file" "$TARGET_DIR/frontend-checklist.mdc"; then
say "✓ Cursor: already at $VERSION, skipping (use --force to reinstall)"
return
fi
mkdir -p "$TARGET_DIR"
fetch_verified "$ASSET_CURSOR" "$TMP/frontend-checklist.mdc"
BACKUP_TARGET="$TARGET_DIR/frontend-checklist.mdc"
if [ -f "$TARGET_DIR/frontend-checklist.mdc" ]; then
BACKUP="$TARGET_DIR/frontend-checklist.mdc.bak.$(date +%s)"
mv "$TARGET_DIR/frontend-checklist.mdc" "$BACKUP"
fi
mv "$TMP/frontend-checklist.mdc" "$TARGET_DIR/frontend-checklist.mdc"
printf '%s\n' "$VERSION" > "$version_file"
say "✓ Cursor: installed rule to $TARGET_DIR/frontend-checklist.mdc"
}
cursor_uninstall() {
rm -f "$TARGET_DIR/frontend-checklist.mdc" "$TARGET_DIR/frontend-checklist.mdc.version"
# Only remove directory if we created it and it's now empty.
rmdir "$TARGET_DIR" 2>/dev/null || true
say "✓ Cursor: removed rule file"
}
# --- Claude / Kiro path: unpack tarball into skills dir ---
skill_install() {
if is_same_version "$TARGET_DIR/frontend-checklist/.version" "$TARGET_DIR/frontend-checklist/SKILL.md"; then
say "✓ ${TOOL}: already at $VERSION, skipping (use --force to reinstall)"
return
fi
install_tarball
say "✓ ${TOOL}: installed to $INSTALL_PATH"
}
skill_uninstall() {
rm -rf "$TARGET_DIR/frontend-checklist"
say "✓ ${TOOL}: removed $TARGET_DIR/frontend-checklist"
}
# --- Dispatch ---
if [ "$ACTION" = "uninstall" ]; then
case "$TOOL" in
codex) codex_uninstall ;;
cursor) cursor_uninstall ;;
*) skill_uninstall ;;
esac
exit 0
fi
say "→ installing frontend-checklist ($VERSION) for $TOOL ($SCOPE)..."
case "$TOOL" in
codex) codex_install ;;
cursor) cursor_install ;;
*) skill_install ;;
esac
case "$TOOL" in
claude) say "提示:重启 Claude Code 后输入 /frontend-checklist 或 '按清单 review' 触发。" ;;
kiro) say "提示:重启 Kiro 后输入 /frontend-checklist 或 '按清单 review' 触发。" ;;
cursor) say "提示:在 Cursor 里对话时说 '按前端清单 review' 即可触发。" ;;
codex) say "提示:Codex 会在下次会话读取 AGENTS.md,直接说 '按前端清单 review' 触发。" ;;
esac