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:
@@ -17,6 +17,10 @@
|
|||||||
- Пример конфига в README приведён в соответствие с реальным `config/company.json`.
|
- Пример конфига в README приведён в соответствие с реальным `config/company.json`.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- `uninstall.sh` — полное удаление кита одной командой с подтверждением
|
||||||
|
(пакет+клон, публичные скилы, `~/.config/pi-kit` с токенами, блок в shell-rc;
|
||||||
|
флаги `PI_KIT_UNINSTALL`/`PI_KIT_KEEP_CONFIG`/`PI_KIT_KEEP_SKILLS`); покрыт тестами
|
||||||
|
(`test/uninstall.test.sh`, изолированный `$HOME`).
|
||||||
- Тесты: company-context (normalize/fetch/truncation), mcp-bridge (disabled-путь),
|
- Тесты: company-context (normalize/fetch/truncation), mcp-bridge (disabled-путь),
|
||||||
табличные кейсы permission-gate, shell-тест `create-mr.sh` (парсинг remote, dry-run).
|
табличные кейсы permission-gate, shell-тест `create-mr.sh` (парсинг remote, dry-run).
|
||||||
- `package-lock.json` закоммичен (воспроизводимая установка).
|
- `package-lock.json` закоммичен (воспроизводимая установка).
|
||||||
|
|||||||
@@ -124,9 +124,10 @@ pi-kit/
|
|||||||
│ └── lib/ # общий код (secrets, audit, company-config)
|
│ └── lib/ # общий код (secrets, audit, company-config)
|
||||||
├── skills/ # SKILL.md-скиллы (jira-workflow/repo-map/docs-map + <lang>-standards)
|
├── skills/ # SKILL.md-скиллы (jira-workflow/repo-map/docs-map + <lang>-standards)
|
||||||
├── prompts/ # шаблоны команд (/bugfix, /feature, /review, /rfc)
|
├── prompts/ # шаблоны команд (/bugfix, /feature, /review, /rfc)
|
||||||
├── test/ # guardrails.test.ts (node) + shell.test.sh (create-mr.sh)
|
├── test/ # guardrails.test.ts (node) + shell/uninstall shell-тесты
|
||||||
├── docs/mcp.md # документация MCP-интеграций
|
├── docs/mcp.md # документация MCP-интеграций
|
||||||
└── install.sh # bootstrap для новых сотрудников
|
├── install.sh # bootstrap для новых сотрудников
|
||||||
|
└── uninstall.sh # полное удаление кита (с подтверждением)
|
||||||
```
|
```
|
||||||
|
|
||||||
Формат: [packages](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md),
|
Формат: [packages](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md),
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "node --experimental-strip-types test/guardrails.test.ts && bash test/shell.test.sh"
|
"test": "node --experimental-strip-types test/guardrails.test.ts && bash test/shell.test.sh && bash test/uninstall.test.sh"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@earendil-works/pi-ai": "*",
|
"@earendil-works/pi-ai": "*",
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Tests for uninstall.sh against a throwaway $HOME (no real machine state touched).
|
||||||
|
# Run via `npm test` or directly: bash test/uninstall.test.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
UNINSTALL="$ROOT/uninstall.sh"
|
||||||
|
FAILS=0
|
||||||
|
|
||||||
|
check() { # <name> <bash-expression>
|
||||||
|
local name="$1"
|
||||||
|
if bash -c "$2"; then
|
||||||
|
echo "PASS $name"
|
||||||
|
else
|
||||||
|
echo "FAIL $name"
|
||||||
|
FAILS=$((FAILS + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fresh fake home with everything the kit can leave behind.
|
||||||
|
setup_fake_home() {
|
||||||
|
export HOME="$(mktemp -d)"
|
||||||
|
export SHELL=/bin/bash
|
||||||
|
export PI_KIT_UNINSTALL=1
|
||||||
|
export PI_KIT_KEEP_CONFIG=0
|
||||||
|
export PI_KIT_KEEP_SKILLS=0
|
||||||
|
|
||||||
|
mkdir -p "$HOME/.pi/agent/git/git.codelab.vc/ai/pi-kit"
|
||||||
|
mkdir -p "$HOME/.agents/skills"
|
||||||
|
mkdir -p "$HOME/.config/pi-kit"
|
||||||
|
|
||||||
|
cat >"$HOME/.pi/agent/settings.json" <<'JSON'
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
"git:github.com/someone/other",
|
||||||
|
{ "source": "git:git.codelab.vc/ai/pi-kit@stable", "skills": ["skills/go-standards"] },
|
||||||
|
"git:git.codelab.vc/ai/pi-kit@beta"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
echo "export JIRA_TOKEN='dummy'" >"$HOME/.config/pi-kit/env.sh"
|
||||||
|
cat >"$HOME/.agents/skills/grill-me" <<'EOF'
|
||||||
|
skill content
|
||||||
|
EOF
|
||||||
|
cat >"$HOME/.bashrc" <<'RC'
|
||||||
|
# user content
|
||||||
|
export FOO=1
|
||||||
|
|
||||||
|
# >>> pi-kit >>>
|
||||||
|
[ -f "$HOME/.config/pi-kit/env.sh" ] && . "$HOME/.config/pi-kit/env.sh"
|
||||||
|
# <<< pi-kit <<<
|
||||||
|
|
||||||
|
# more user content
|
||||||
|
RC
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1. Full uninstall --------------------------------------------------------
|
||||||
|
setup_fake_home
|
||||||
|
bash "$UNINSTALL" >/dev/null
|
||||||
|
check "uninstall: settings.json keeps other packages" "grep -q 'git:github.com/someone/other' \"$HOME/.pi/agent/settings.json\""
|
||||||
|
check "uninstall: settings.json drops object-form pi-kit" "! grep -q 'pi-kit' \"$HOME/.pi/agent/settings.json\""
|
||||||
|
check "uninstall: clone dir removed" "test ! -e \"$HOME/.pi/agent/git/git.codelab.vc/ai/pi-kit\""
|
||||||
|
check "uninstall: config dir removed" "test ! -e \"$HOME/.config/pi-kit\""
|
||||||
|
check "uninstall: rc block removed" "! grep -q 'pi-kit' \"$HOME/.bashrc\""
|
||||||
|
check "uninstall: rc user content preserved" "grep -q 'export FOO=1' \"$HOME/.bashrc\" && grep -q 'more user content' \"$HOME/.bashrc\""
|
||||||
|
check "uninstall: public skill removed" "test ! -e \"$HOME/.agents/skills/grill-me\""
|
||||||
|
|
||||||
|
# --- 2. Keep config (secrets + rc) --------------------------------------------
|
||||||
|
setup_fake_home
|
||||||
|
PI_KIT_KEEP_CONFIG=1 bash "$UNINSTALL" >/dev/null
|
||||||
|
check "keep-config: env.sh kept" "test -e \"$HOME/.config/pi-kit/env.sh\""
|
||||||
|
check "keep-config: rc block kept" "grep -q 'pi-kit' \"$HOME/.bashrc\""
|
||||||
|
check "keep-config: package still removed" "! grep -q 'pi-kit' \"$HOME/.pi/agent/settings.json\""
|
||||||
|
|
||||||
|
# --- 3. Keep skills ------------------------------------------------------------
|
||||||
|
setup_fake_home
|
||||||
|
PI_KIT_KEEP_SKILLS=1 bash "$UNINSTALL" >/dev/null
|
||||||
|
check "keep-skills: skill kept" "test -e \"$HOME/.agents/skills/grill-me\""
|
||||||
|
|
||||||
|
# --- 4. Non-interactive without flag must refuse ---------------------------------
|
||||||
|
setup_fake_home
|
||||||
|
PI_KIT_UNINSTALL=0 bash "$UNINSTALL" >/dev/null 2>&1 && RC=0 || RC=$?
|
||||||
|
check "uninstall: refuses without confirmation flag in non-interactive mode" "test \"$RC\" -ne 0"
|
||||||
|
check "uninstall: nothing removed on refusal" "grep -q 'pi-kit' \"$HOME/.pi/agent/settings.json\" && test -e \"$HOME/.config/pi-kit/env.sh\""
|
||||||
|
|
||||||
|
# --- 5. Idempotent re-run -------------------------------------------------------
|
||||||
|
setup_fake_home
|
||||||
|
bash "$UNINSTALL" >/dev/null
|
||||||
|
bash "$UNINSTALL" >/dev/null 2>&1 || true
|
||||||
|
check "uninstall: re-run is harmless" "test ! -e \"$HOME/.config/pi-kit\" && ! grep -q 'pi-kit' \"$HOME/.pi/agent/settings.json\""
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ "$FAILS" -eq 0 ]; then
|
||||||
|
echo "uninstall tests: ALL PASS"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "uninstall tests: $FAILS FAILED"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
+196
@@ -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
|
||||||
Reference in New Issue
Block a user