Files
wehub-resource-sync 5296d0e97c
CI / Ban suppressions and legacy annotations (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / ruff-check (push) Has been cancelled
CI / ruff-format (push) Has been cancelled
CI / ty (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:44 +08:00

244 lines
5.9 KiB
Bash

#!/bin/sh
set -eu
PACKAGE_NAME="free-claude-code"
FCC_HOME_DIRNAME=".fcc"
FCC_COMMANDS="fcc-server fcc-claude fcc-codex fcc-pi fcc-init free-claude-code"
dry_run=0
uv_tool_bin=""
show_usage() {
cat <<'USAGE'
Usage: uninstall.sh [options]
Removes the Free Claude Code uv tool and deletes ~/.fcc/ after removal is verified.
Does not remove uv, Claude Code, Codex, Pi, the uv-managed Python runtime, or shared PATH entries.
Options:
--dry-run Print commands without running them.
--help Show this help text.
USAGE
}
fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
step() {
printf '\n==> %s\n' "$1"
}
quote_arg() {
case "$1" in
*[!A-Za-z0-9_./:@%+=,-]*|"")
escaped=$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g')
printf '"%s"' "$escaped"
;;
*)
printf '%s' "$1"
;;
esac
}
print_command() {
printf '+'
for arg in "$@"; do
printf ' '
quote_arg "$arg"
done
printf '\n'
}
run() {
print_command "$@"
if [ "$dry_run" -eq 1 ]; then
return 0
fi
if "$@"; then
return 0
else
status=$?
fi
fail "Command failed with exit code $status: $1"
}
is_missing_uv_tool_error() {
normalized=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')
case "$normalized" in
*"$PACKAGE_NAME"*"is not installed"*) return 0 ;;
*) return 1 ;;
esac
}
add_path_entry() {
[ -n "$1" ] || return 0
case ":$PATH:" in
*":$1:"*) ;;
*) PATH="$1:$PATH" ;;
esac
}
add_known_uv_paths() {
if [ -n "${XDG_BIN_HOME:-}" ]; then
add_path_entry "$XDG_BIN_HOME"
fi
add_path_entry "$HOME/.local/bin"
add_path_entry "$HOME/.cargo/bin"
export PATH
hash -r 2>/dev/null || true
}
is_fcc_command_running() {
command_name=$1
if command -v pgrep >/dev/null 2>&1; then
if pgrep -x "$command_name" >/dev/null 2>&1; then
return 0
fi
if pgrep -f "(^|/)${command_name}( |$)" >/dev/null 2>&1; then
return 0
fi
return 1
fi
ps -A -o comm= 2>/dev/null | grep -qx "$command_name"
}
assert_no_fcc_processes_running() {
running=""
for command_name in $FCC_COMMANDS; do
if is_fcc_command_running "$command_name"; then
running="${running} ${command_name}"
fi
done
if [ -n "$running" ]; then
fail "Free Claude Code is still running (${running# }). Stop those processes, then rerun uninstall."
fi
}
initialize_uv_context() {
add_known_uv_paths
if [ "$dry_run" -eq 1 ]; then
print_command uv tool dir --bin
return 0
fi
if ! command -v uv >/dev/null 2>&1; then
fail "uv is required to remove the Free Claude Code tool. Install uv, then rerun this uninstaller; ~/.fcc was not deleted."
fi
print_command uv tool dir --bin
if uv_tool_bin=$(uv tool dir --bin); then
:
else
status=$?
fail "Could not determine the uv tool bin directory (exit code $status); ~/.fcc was not deleted."
fi
[ -n "$uv_tool_bin" ] || fail "uv returned an empty tool bin directory; ~/.fcc was not deleted."
}
uninstall_free_claude_code() {
print_command uv tool uninstall "$PACKAGE_NAME"
if [ "$dry_run" -eq 1 ]; then
return 0
fi
if output=$(uv tool uninstall "$PACKAGE_NAME" 2>&1); then
if [ -n "$output" ]; then
printf '%s\n' "$output"
fi
return 0
else
status=$?
fi
if is_missing_uv_tool_error "$output"; then
printf 'Free Claude Code uv tool is already absent; verifying its entry points.\n'
return 0
fi
if [ -n "$output" ]; then
printf '%s\n' "$output" >&2
fi
fail "uv tool uninstall $PACKAGE_NAME failed with exit code $status; ~/.fcc was not deleted."
}
verify_fcc_commands_removed() {
if [ "$dry_run" -eq 1 ]; then
printf '+ verify all Free Claude Code entry points are absent from the uv tool bin directory\n'
return 0
fi
remaining=""
for command_name in $FCC_COMMANDS; do
command_path="$uv_tool_bin/$command_name"
if [ -e "$command_path" ] || [ -L "$command_path" ]; then
remaining="${remaining} ${command_path}"
fi
done
if [ -n "$remaining" ]; then
fail "Free Claude Code entry points remain after uv uninstall:${remaining}; ~/.fcc was not deleted."
fi
}
purge_fcc_home() {
fcc_home="$HOME/$FCC_HOME_DIRNAME"
if [ ! -e "$fcc_home" ]; then
printf 'No FCC config directory at %s; skipping purge.\n' "$fcc_home"
return 0
fi
run rm -rf "$fcc_home"
if [ "$dry_run" -eq 0 ] && [ -e "$fcc_home" ]; then
fail "FCC config directory still exists after deletion: $fcc_home"
fi
}
parse_args() {
while [ "$#" -gt 0 ]; do
case "$1" in
--dry-run)
dry_run=1
;;
--help|-h)
show_usage
exit 0
;;
*)
show_usage >&2
fail "unknown option: $1"
;;
esac
shift
done
}
parse_args "$@"
[ -n "${HOME:-}" ] || fail "HOME is not set; cannot locate Free Claude Code data."
step "Checking for running Free Claude Code processes"
assert_no_fcc_processes_running
step "Locating the uv-managed Free Claude Code installation"
initialize_uv_context
step "Removing the Free Claude Code uv tool"
uninstall_free_claude_code
step "Verifying Free Claude Code entry points were removed"
verify_fcc_commands_removed
step "Purging FCC config and data from ~/.fcc"
purge_fcc_home
if [ "$dry_run" -eq 1 ]; then
printf '\nDry run complete. No changes were made.\n'
else
printf '\nFree Claude Code has been removed and verified.\n'
printf 'uv, Claude Code, Codex, Pi, the uv-managed Python runtime, and shared PATH entries were left installed.\n'
fi