-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuninstall
More file actions
executable file
·280 lines (246 loc) · 8.94 KB
/
Copy pathuninstall
File metadata and controls
executable file
·280 lines (246 loc) · 8.94 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
#!/usr/bin/env bash
# vibestack uninstall — remove skills from selected targets, vibe-* binaries,
# and (optionally) state.
# v1.4.0: multi-target uninstall mirrors install --target= semantics.
set -uo pipefail
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILLS_SRC="$REPO_DIR/skills"
VIBE_HOME="${VIBESTACK_HOME:-$HOME/.vibestack}"
VIBE_BIN="$VIBE_HOME/bin"
usage() {
cat <<'EOF'
Usage: ./uninstall [options]
Options:
--target=<list> Comma-separated targets: claude, cursor, kiro, codex, or all.
--scope=<s> user (default) or project — must match how you installed.
--project-root=<d> Project root for --scope=project (default: $PWD).
Default: claude (preserves v1.3.x behavior).
--delete-state Bypass the prompt and delete ~/.vibestack/ (learnings,
analytics, project state).
-h, --help Show this help and exit.
Examples:
./uninstall # Claude Code only
./uninstall --target=all # Claude + Cursor + Kiro + Codex
./uninstall --target=cursor # Cursor only
./uninstall --target=all --delete-state # full removal, non-interactive
EOF
}
# Parse args
TARGETS_RAW=""
DELETE_STATE_FLAG=0
SCOPE="user"
PROJECT_ROOT=""
for arg in "$@"; do
case "$arg" in
--target=*)
TARGETS_RAW="${arg#*=}"
;;
--scope=*)
SCOPE="${arg#*=}"
;;
--project-root=*)
PROJECT_ROOT="${arg#*=}"
;;
--delete-state)
DELETE_STATE_FLAG=1
;;
-h|--help)
usage
exit 0
;;
*)
echo "ERROR: unknown argument: $arg" >&2
usage >&2
exit 2
;;
esac
done
# Resolve target list
declare -a TARGETS
if [ -z "$TARGETS_RAW" ]; then
TARGETS=("claude")
else
if [ "$TARGETS_RAW" = "all" ]; then
TARGETS=("claude" "cursor" "kiro" "codex")
else
IFS=',' read -ra TARGETS <<< "$TARGETS_RAW"
fi
for t in "${TARGETS[@]}"; do
case "$t" in
claude|cursor|kiro|codex) ;;
*)
echo "ERROR: unknown target '$t'. Valid: claude, cursor, kiro, codex, all." >&2
exit 2
;;
esac
done
fi
# Scope resolution (must mirror install's --scope semantics)
case "$SCOPE" in
user) SCOPE_BASE="$HOME" ;;
project)
PROJECT_ROOT="${PROJECT_ROOT:-${VIBESTACK_PROJECT_ROOT:-$PWD}}"
case "$PROJECT_ROOT" in "~") PROJECT_ROOT="$HOME" ;; "~/"*) PROJECT_ROOT="$HOME/${PROJECT_ROOT#\~/}" ;; esac
[ -d "$PROJECT_ROOT" ] || { echo "ERROR: project root not found: $PROJECT_ROOT" >&2; exit 2; }
PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd -P)" || { echo "ERROR: cannot resolve project root" >&2; exit 2; }
SCOPE_BASE="$PROJECT_ROOT"
echo "Scope: project ($PROJECT_ROOT)"
;;
*) echo "ERROR: invalid --scope '$SCOPE' (user|project)" >&2; exit 2 ;;
esac
# Map target → root (must match install's mapping)
target_root() {
case "$1" in
claude) echo "$SCOPE_BASE/.claude/skills" ;;
cursor) echo "$SCOPE_BASE/.cursor/skills" ;;
kiro) echo "$SCOPE_BASE/.kiro/skills" ;;
# Codex reads skills from .agents/skills, not .codex/skills — ~/.codex holds
# its config only. Matches install's TARGET_ROOT / TARGET_PROJECT_REL.
codex) echo "$SCOPE_BASE/.agents/skills" ;;
*) echo "" ;;
esac
}
# ───────────────────────────────────────────────────────────────────────────
# Per-skill uninstall (v1.4.0 — mirrors install_skill_to_target's split)
# Removes:
# - <root>/<skill_name>/SKILL.md (regular file written by renderer)
# - <root>/<skill_name>/.vibe-render.json (sidecar, no-op if absent)
# - All symlinks in <root>/<skill_name>/ (bin/, sub-docs)
# - <root>/<skill_name>/ if empty
# Preserves:
# - Any non-symlink files the user manually placed in <root>/<skill_name>/
# (per the v1.3.0 contract — see learning vibestack_uninstall_only_removes_symlinks)
# ───────────────────────────────────────────────────────────────────────────
uninstall_skill_from_target() {
local skill_dir="$1"
local target_root="$2"
local skill_name target
skill_name=$(grep -m1 '^name:' "$skill_dir/SKILL.md" 2>/dev/null \
| sed 's/^name:[[:space:]]*//' | tr -d '[:space:]')
[ -z "$skill_name" ] && skill_name="$(basename "$skill_dir")"
target="$target_root/$skill_name"
if [ ! -d "$target" ]; then
return 1 # not found
fi
# Remove every symlink we created (sub-docs, bin/). Leave any other
# non-symlink files alone (they didn't come from us).
for item in "$target"/* "$target"/.[!.]* "$target"/..?*; do
[ -e "$item" ] || [ -L "$item" ] || continue
if [ -L "$item" ]; then
rm -f "$item"
fi
done
# Remove the rendered SKILL.md (regular file) and its sidecar JSON, if present.
# Scoped narrowly to the canonical paths we created.
if [ -f "$target/SKILL.md" ] && [ ! -L "$target/SKILL.md" ]; then
rm -f "$target/SKILL.md"
fi
rm -f "$target/.vibe-render.json"
# Try to remove the directory. If it's not empty (user added files), leave it.
if [ -z "$(ls -A "$target" 2>/dev/null)" ]; then
rmdir "$target" 2>/dev/null
echo " - removed $skill_name"
return 0
else
echo " - kept $skill_name (contains non-vibestack files)"
return 2 # kept (had user files)
fi
}
# ───────────────────────────────────────────────────────────────────────────
# Main uninstall flow
# ───────────────────────────────────────────────────────────────────────────
echo "Uninstalling vibestack..."
echo ""
total_removed=0
total_not_found=0
total_not_empty=0
for target in "${TARGETS[@]}"; do
root=$(target_root "$target")
if [ -z "$root" ]; then
echo "ERROR: could not resolve root for target '$target'" >&2
exit 3
fi
if [ ! -d "$root" ]; then
echo "Target: $target ($root) — not found, skipping"
continue
fi
echo "Target: $target ($root)"
removed=0
not_found=0
not_empty=0
for skill_dir in "$SKILLS_SRC"/*/; do
[ -f "$skill_dir/SKILL.md" ] || continue
set +e
uninstall_skill_from_target "$skill_dir" "$root"
rc=$?
set -e
case "$rc" in
0) removed=$((removed + 1)) ;;
1) not_found=$((not_found + 1)) ;;
2) removed=$((removed + 1)); not_empty=$((not_empty + 1)) ;;
esac
done
# The install manifest is a regular file at the root of the target, written
# so the next install can tell a withdrawn skill from someone else's. Nothing
# reads it once the pack is gone, and leaving it behind would leak across an
# uninstall the way the sidecars used to.
rm -f "$root/.vibestack-manifest"
echo " $target: $removed processed, $not_found already absent, $not_empty kept (had user files)"
total_removed=$((total_removed + removed))
total_not_found=$((total_not_found + not_found))
total_not_empty=$((total_not_empty + not_empty))
done
echo ""
echo "Skills total: $total_removed processed across ${#TARGETS[@]} target(s), $total_not_found already absent, $total_not_empty kept (had user files)"
# Remove vibe-* binary copies under ~/.vibestack/bin/
echo ""
bin_removed=0
if [ -d "$VIBE_BIN" ]; then
for bin_file in "$VIBE_BIN"/vibe-* "$VIBE_BIN/vibestack"; do
[ -f "$bin_file" ] || continue
rm -f "$bin_file"
bin_removed=$((bin_removed + 1))
done
rm -f "$VIBE_HOME/version"
echo "Binaries: $bin_removed removed from $VIBE_BIN"
if [ -z "$(ls -A "$VIBE_BIN" 2>/dev/null)" ]; then
rmdir "$VIBE_BIN" 2>/dev/null || true
fi
fi
# Decide whether to remove ~/.vibestack/ state.
echo ""
if [ -d "$VIBE_HOME" ] && [ -n "$(ls -A "$VIBE_HOME" 2>/dev/null)" ]; then
echo "State directory: $VIBE_HOME"
echo " Contents:"
for item in "$VIBE_HOME"/*; do
[ -e "$item" ] || continue
echo " $(basename "$item")"
done
echo ""
reply="n"
if [ "$DELETE_STATE_FLAG" = "1" ]; then
reply="y"
echo "(--delete-state flag set; deleting without prompting)"
elif [ -r /dev/tty ] && [ -t 0 ]; then
printf "Delete state directory including learnings and analytics? [y/N] "
read -r reply </dev/tty || reply="n"
else
echo "(non-interactive run — keeping state. Pass --delete-state to force removal.)"
fi
case "$reply" in
y|Y|yes|YES)
rm -rf "$VIBE_HOME"
echo " - deleted $VIBE_HOME"
;;
*)
echo " - kept $VIBE_HOME"
;;
esac
fi
# Tell the user what remains
echo ""
echo "What stays (delete manually if you want a full removal):"
echo " $REPO_DIR (the cloned repo)"
[ -d "$VIBE_HOME" ] && echo " $VIBE_HOME (kept above)"
echo ""
echo "Start a new session of your chosen agent for changes to take effect."