Skip to content

Instantly share code, notes, and snippets.

@felixhaeberle
Created February 24, 2026 10:33
Show Gist options
  • Select an option

  • Save felixhaeberle/90e2ce0ef7ea307e77e355b81b360408 to your computer and use it in GitHub Desktop.

Select an option

Save felixhaeberle/90e2ce0ef7ea307e77e355b81b360408 to your computer and use it in GitHub Desktop.
openclaw / moltbot / clawdbot – complete uninstall script
#!/usr/bin/env bash
set -euo pipefail
# ============================================================
# OpenClaw "One file to rule it all" — FULL PURGE
# Verified names/aliases: openclaw, clawdbot, moltbot
# Related ecosystem name: moltbook
# Verified folders: ~/.openclaw (incl. ~/.openclaw/openclaw.json, ~/.openclaw/workspace)
#
# macOS + Linux. Interactive. Idempotent. Conservative scanning.
#
# RUN WITH:
# chmod +x purge-openclaw.sh
# ./purge-openclaw.sh
#
# ============================================================
TITLE="OpenClaw Full Purge"
TARGET_NAMES=(openclaw clawdbot moltbot moltbook)
PRIMARY_DIRS=(
"$HOME/.openclaw"
"$HOME/.config/openclaw"
"$HOME/.cache/openclaw"
"$HOME/.local/share/openclaw"
)
# Optional related dirs (only removed if found):
OPTIONAL_DIRS=(
"$HOME/.clawdbot"
"$HOME/.moltbot"
"$HOME/.moltbook"
"$HOME/.config/clawdbot"
"$HOME/.config/moltbot"
"$HOME/.config/moltbook"
"$HOME/.cache/clawdbot"
"$HOME/.cache/moltbot"
"$HOME/.cache/moltbook"
"$HOME/.local/share/clawdbot"
"$HOME/.local/share/moltbot"
"$HOME/.local/share/moltbook"
)
# Common binary locations to check (plus whatever is on PATH via `command -v`)
BIN_CANDIDATES=(
"/usr/local/bin"
"/opt/homebrew/bin"
"$HOME/.local/bin"
"$HOME/.bun/bin"
"$HOME/.npm-global/bin"
"$HOME/.volta/bin"
"$HOME/.asdf/shims"
)
# macOS persistence locations
MAC_LAUNCH_AGENTS=("$HOME/Library/LaunchAgents" "/Library/LaunchAgents")
MAC_LAUNCH_DAEMONS=("/Library/LaunchDaemons")
# Linux persistence locations
LINUX_SYSTEMD_USER_DIRS=("$HOME/.config/systemd/user")
LINUX_CRON_DIRS=("/etc/cron.d" "/etc/cron.daily" "/etc/cron.hourly" "/etc/cron.weekly" "/etc/cron.monthly")
bold() { printf "\033[1m%s\033[0m\n" "$*"; }
info() { printf " • %s\n" "$*"; }
ok() { printf " \033[32m✓ %s\033[0m\n" "$*"; }
warn() { printf " \033[33m⚠ %s\033[0m\n" "$*"; }
have() { command -v "$1" >/dev/null 2>&1; }
confirm() {
local prompt="$1"
read -r -p "$prompt [y/N] " ans
[[ "${ans:-}" =~ ^[Yy]$ ]]
}
rm_any() {
local p="$1"
if [[ -e "$p" || -L "$p" ]]; then
rm -rf "$p"
ok "Removed: $p"
else
info "Not found: $p"
fi
}
list_matches_in_dir() {
local dir="$1"
[[ -d "$dir" ]] || return 0
local found=0
for name in "${TARGET_NAMES[@]}"; do
if ls "$dir" 2>/dev/null | grep -i -q "$name"; then
found=1
break
fi
done
[[ "$found" -eq 1 ]] || return 0
warn "Matches under: $dir"
# show matching entries (case-insensitive)
ls -1 "$dir" 2>/dev/null | while read -r entry; do
for name in "${TARGET_NAMES[@]}"; do
if printf "%s" "$entry" | grep -i -q "$name"; then
info "$dir/$entry"
break
fi
done
done
}
delete_matches_in_dir() {
local dir="$1"
[[ -d "$dir" ]] || return 0
for entry in "$dir"/*; do
[[ -e "$entry" ]] || continue
local base
base="$(basename "$entry")"
for name in "${TARGET_NAMES[@]}"; do
if printf "%s" "$base" | grep -i -q "$name"; then
rm_any "$entry"
break
fi
done
done
}
uninstall_global_packages() {
bold "1) Uninstall global packages (if installed)"
local any=0
if have npm; then
any=1
npm uninstall -g openclaw clawdbot moltbot moltbook >/dev/null 2>&1 || info "npm: none of the packages were installed globally"
else
info "npm not found"
fi
if have pnpm; then
any=1
pnpm remove -g openclaw clawdbot moltbot moltbook >/dev/null 2>&1 || info "pnpm: none of the packages were installed globally"
else
info "pnpm not found"
fi
if have yarn; then
any=1
yarn global remove openclaw clawdbot moltbot moltbook >/dev/null 2>&1 || info "yarn: none of the packages were installed globally"
else
info "yarn not found"
fi
if have bun; then
any=1
bun remove openclaw clawdbot moltbot moltbook >/dev/null 2>&1 || info "bun: none of the packages were installed globally"
else
info "bun not found"
fi
[[ "$any" -eq 1 ]] && ok "Global uninstall attempts complete."
}
remove_binaries() {
bold "2) Remove binaries / shims"
# Remove resolved PATH hits first
for name in "${TARGET_NAMES[@]}"; do
if have "$name"; then
local p
p="$(command -v "$name")"
warn "Found on PATH: $name -> $p"
if confirm "Remove this file?"; then
rm_any "$p"
else
info "Skipped: $p"
fi
fi
done
# Remove common candidate locations
for d in "${BIN_CANDIDATES[@]}"; do
[[ -d "$d" ]] || continue
for name in "${TARGET_NAMES[@]}"; do
rm_any "$d/$name"
done
done
}
purge_dirs() {
bold "3) Purge verified config/cache/data directories"
warn "These are safe targets commonly used by OpenClaw installs, incl. ~/.openclaw (config + workspace)."
for d in "${PRIMARY_DIRS[@]}"; do
rm_any "$d"
done
bold "4) Purge optional legacy/related directories (only if present)"
for d in "${OPTIONAL_DIRS[@]}"; do
rm_any "$d"
done
}
purge_persistence() {
bold "5) Check persistence (LaunchAgents/systemd/cron) and remove matching entries"
# macOS LaunchAgents/Daemons
if [[ "$(uname -s)" == "Darwin" ]]; then
for dir in "${MAC_LAUNCH_AGENTS[@]}" "${MAC_LAUNCH_DAEMONS[@]}"; do
list_matches_in_dir "$dir"
if [[ -d "$dir" ]]; then
if confirm "Delete matching LaunchAgent/Daemon entries under $dir ?"; then
delete_matches_in_dir "$dir"
else
info "Skipped: $dir"
fi
fi
done
fi
# Linux systemd user units
if [[ -d "${LINUX_SYSTEMD_USER_DIRS[0]}" ]]; then
for dir in "${LINUX_SYSTEMD_USER_DIRS[@]}"; do
list_matches_in_dir "$dir"
if confirm "Delete matching systemd user unit files under $dir ?"; then
delete_matches_in_dir "$dir"
if have systemctl; then
warn "If any units existed, you may want to run:"
info "systemctl --user daemon-reload"
info "systemctl --user reset-failed"
fi
else
info "Skipped: $dir"
fi
done
fi
# Cron snippets (best-effort; system locations may require sudo)
for dir in "${LINUX_CRON_DIRS[@]}"; do
[[ -d "$dir" ]] || continue
list_matches_in_dir "$dir"
warn "Cron directory may require sudo: $dir"
if confirm "Attempt to delete matching cron entries under $dir (may fail without sudo)?"; then
delete_matches_in_dir "$dir" || warn "Some deletions may have failed (permissions)."
else
info "Skipped: $dir"
fi
done
}
purge_project_node_modules() {
bold "6) Optional: remove local node_modules occurrences (CURRENT directory tree only)"
warn "This scans ONLY under: $(pwd)"
if ! confirm "Scan and delete folders with names matching openclaw/clawdbot/moltbot/moltbook inside node_modules?"; then
info "Skipped node_modules cleanup."
return 0
fi
# Only delete directories inside node_modules that match target names
while IFS= read -r p; do
rm_any "$p"
done < <(find . -type d -path "*/node_modules/*" \( \
-iname "*openclaw*" -o -iname "*clawdbot*" -o -iname "*moltbot*" -o -iname "*moltbook*" \
\) 2>/dev/null || true)
ok "node_modules cleanup complete."
}
verify() {
bold "7) Verification"
for name in "${TARGET_NAMES[@]}"; do
if have "$name"; then
warn "Still found on PATH: $name -> $(command -v "$name")"
warn "Run this for deeper visibility:"
info "type -a $name"
else
ok "Not on PATH: $name"
fi
done
bold "8) Quick check: ~/.openclaw"
if [[ -e "$HOME/.openclaw" ]]; then
warn "Still exists: $HOME/.openclaw"
else
ok "~/.openclaw removed"
fi
}
main() {
bold "$TITLE"
warn "Targets (verified): ${TARGET_NAMES[*]}"
warn "This will remove files/directories and may require sudo for system locations."
if ! confirm "Proceed?"; then
echo "Aborted."
exit 0
fi
uninstall_global_packages
remove_binaries
purge_dirs
purge_persistence
purge_project_node_modules
verify
bold "Done."
info "If you removed shell/PATH references manually, restart your terminal or run: exec \$SHELL -l"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment