feat(uninstall): one-command full uninstall with confirmation

- uninstall.sh: removes pi-kit from settings.json (string + gated object
  entries), the package clone, kit-installed public skills, ~/.config/pi-kit
  (env.sh with tokens, audit.jsonl) and the shell-rc sourcing block
- confirmation prompt by default; PI_KIT_UNINSTALL=1 for non-interactive,
  PI_KIT_KEEP_CONFIG=1 / PI_KIT_KEEP_SKILLS=1 to retain parts
- works without the pi binary (settings edited directly); notes how to
  remove pi itself
- tests: test/uninstall.test.sh against a throwaway $HOME (14 checks:
  full/partial removal, refusal without flag, idempotent re-run), wired
  into npm test
This commit is contained in:
Aleksey Shakhmatov
2026-08-06 11:45:50 +03:00
parent 7b25f0eea1
commit 035bededb0
5 changed files with 303 additions and 3 deletions
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env bash
#
# pi-kit uninstall — removes the corporate kit from this machine completely.
# Idempotent: safe to re-run; missing pieces are skipped with a note.
#
# Usage (one command, asks for confirmation):
# curl -fsSL https://git.codelab.vc/ai/pi-kit/raw/branch/stable/uninstall.sh | bash
# # or, from a checkout:
# ./uninstall.sh
#
# What it removes:
# 1. the pi-kit package entry from ~/.pi/agent/settings.json and its clone
# (~/.pi/agent/git/git.codelab.vc/ai/pi-kit);
# 2. public skills the kit installed into ~/.agents/skills/
# (grill-me, grill-with-docs, code-review, diagnosing-bugs, frontend-design);
# 3. ~/.config/pi-kit (env.sh with tokens, audit.jsonl) and the sourcing block
# it added to your shell rc — only if you confirm (or unless kept).
#
# Non-interactive / skip confirmation: PI_KIT_UNINSTALL=1
# Keep secrets & rc block: PI_KIT_KEEP_CONFIG=1
# Keep public skills: PI_KIT_KEEP_SKILLS=1
#
# Pi itself is NOT removed (it may be used outside the kit). See the final note.
#
set -euo pipefail
# Mirrors the list in install.sh (COMMON_PUBLIC_SKILLS + PROFILE_PUBLIC).
PUBLIC_SKILLS=("grill-me" "grill-with-docs" "code-review" "diagnosing-bugs" "frontend-design")
SETTINGS="$HOME/.pi/agent/settings.json"
CONFIG_DIR="$HOME/.config/pi-kit"
ENV_FILE="$CONFIG_DIR/env.sh"
RC_MARK=">>> pi-kit >>>"
RC_MARK_END="<<< pi-kit <<<"
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*"; }
err() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; }
case "${1:-}" in
-h | --help)
cat <<'EOF'
Удаляет pi-kit полностью: пакет из настроек pi, его клон, публичные скилы,
~/.config/pi-kit (env.sh с токенами) и блок в shell-rc.
Использование: curl -fsSL https://git.codelab.vc/ai/pi-kit/raw/branch/stable/uninstall.sh | bash
Флаги:
PI_KIT_UNINSTALL=1 не спрашивать подтверждение (для CI/автоматизации)
PI_KIT_KEEP_CONFIG=1 оставить ~/.config/pi-kit (токены) и блок в rc
PI_KIT_KEEP_SKILLS=1 оставить публичные скилы в ~/.agents/skills
EOF
exit 0
;;
esac
detect_rc() {
case "$(basename "${SHELL:-}")" in
zsh) echo "$HOME/.zshrc" ;;
bash) echo "$HOME/.bashrc" ;;
*) echo "$HOME/.profile" ;;
esac
}
# --- 1. Confirmation ---------------------------------------------------------
summary_parts=("пакет pi-kit из настроек pi (settings.json + клон)")
if [ "${PI_KIT_KEEP_SKILLS:-0}" != "1" ]; then
summary_parts+=("публичные скилы кита в ~/.agents/skills: ${PUBLIC_SKILLS[*]}")
fi
if [ "${PI_KIT_KEEP_CONFIG:-0}" != "1" ]; then
summary_parts+=("~/.config/pi-kit (env.sh с токенами, audit.jsonl)")
summary_parts+=("блок '${RC_MARK}' в $(detect_rc)")
fi
if [ "${PI_KIT_UNINSTALL:-0}" != "1" ]; then
if [ ! -r /dev/tty ]; then
err "Нет терминала для подтверждения. Для неинтерактивного удаления задай PI_KIT_UNINSTALL=1."
exit 1
fi
{
printf 'Будет удалено:\n'
for s in "${summary_parts[@]}"; do printf ' • %s\n' "$s"; done
printf '\nУдалить pi-kit полностью? [y/N] '
} >/dev/tty
read -r ans </dev/tty || ans=""
case "$ans" in
y | Y | yes | YES) ;;
*)
err "Отменено. Ничего не удалено."
exit 1
;;
esac
else
info "PI_KIT_UNINSTALL=1 — пропускаю подтверждение."
fi
# --- 2. Remove the pi-kit package entry from settings.json -------------------
# Same object-form handling as install.sh gating: entries are either strings or
# {source, skills:[...]}; we drop any whose source contains "pi-kit".
if [ -f "$SETTINGS" ]; then
if command -v node >/dev/null 2>&1; then
SETTINGS_FILE="$SETTINGS" node <<'NODE'
const fs = require("fs");
const file = process.env.SETTINGS_FILE;
const s = JSON.parse(fs.readFileSync(file, "utf8"));
if (!Array.isArray(s.packages)) process.exit(0);
const before = s.packages.length;
s.packages = s.packages.filter((p) => {
const src = typeof p === "string" ? p : (p && p.source);
return !(typeof src === "string" && src.includes("pi-kit"));
});
if (s.packages.length === before) process.exit(0);
fs.writeFileSync(file, JSON.stringify(s, null, 2) + "\n");
console.log("removed pi-kit entry from " + file);
NODE
else
warn "node не найден — пропускаю правку settings.json. Удали пакет вручную:"
warn " pi remove git:git.codelab.vc/ai/pi-kit@<твой канал>"
fi
else
warn "settings.json не найден ($SETTINGS) — пропускаю."
fi
# --- 3. Remove the pi-kit clone -------------------------------------------------
# install.sh installs `git:git.codelab.vc/ai/pi-kit@<channel>` → clone at
# ~/.pi/agent/git/git.codelab.vc/ai/pi-kit. Also catch any other pi-kit clone.
CLONE_DIR="$HOME/.pi/agent/git/git.codelab.vc/ai/pi-kit"
if [ -d "$CLONE_DIR" ]; then
rm -rf "$CLONE_DIR"
info "Удалён клон пакета: $CLONE_DIR"
else
warn "Клон пакета не найден ($CLONE_DIR) — пропускаю."
fi
if [ -d "$HOME/.pi/agent/git" ]; then
while IFS= read -r d; do
if [ "$d" != "$CLONE_DIR" ] && [ -d "$d" ]; then
rm -rf "$d"
info "Удалён дополнительный клон: $d"
fi
done < <(find "$HOME/.pi/agent/git" -maxdepth 4 -type d -name pi-kit 2>/dev/null || true)
fi
# --- 4. Public skills ----------------------------------------------------------
if [ "${PI_KIT_KEEP_SKILLS:-0}" != "1" ]; then
for skill in "${PUBLIC_SKILLS[@]}"; do
dir="$HOME/.agents/skills/$skill"
if [ -e "$dir" ]; then
rm -rf "$dir"
info "Удалён публичный скилл: $dir"
else
info "Скилл не был установлен (пропускаю): $skill"
fi
done
else
info "PI_KIT_KEEP_SKILLS=1 — публичные скилы оставлены."
fi
# --- 5. Config dir (secrets) + shell rc block ---------------------------------
if [ "${PI_KIT_KEEP_CONFIG:-0}" != "1" ]; then
if [ -d "$CONFIG_DIR" ]; then
rm -rf "$CONFIG_DIR"
info "Удалён ~/.config/pi-kit (env.sh с токенами, audit.jsonl)."
else
warn "~/.config/pi-kit не найден — пропускаю."
fi
rc="$(detect_rc)"
if [ -f "$rc" ] && grep -qF "$RC_MARK" "$rc" 2>/dev/null; then
awk -v start="$RC_MARK" -v end="$RC_MARK_END" '
$0 ~ start { skip = 1 }
!skip { print }
$0 ~ end { skip = 0 }
' "$rc" >"$rc.tmp" && mv "$rc.tmp" "$rc"
info "Убран блок pi-kit из $rc."
else
warn "Блок pi-kit в $(detect_rc) не найден — пропускаю."
fi
else
info "PI_KIT_KEEP_CONFIG=1 — ~/.config/pi-kit и rc-блок оставлены."
fi
# --- 6. Final note --------------------------------------------------------------
cat <<EOF
────────────────────────────────────────────────────────
Готово. pi-kit удалён.
Pi остаётся установленным (может использоваться вне кита).
Удалить сам Pi: npm uninstall -g @earendil-works/pi-coding-agent
(или pnpm remove -g / yarn global remove / bun uninstall -g)
После этого можно удалить и ~/.pi/agent (сессии, креды) — командой rm вручную.
Если что-то осталось (например, ты ставил пакет вручную под другим именем) —
покажет: pi list
────────────────────────────────────────────────────────
EOF