Two operator-requested features: 1. `larry uninstall` / uninstall-larry.sh — there was no uninstaller before. Reverses install-larry.sh exactly: removes $LARRY_HOME (bundle + bin/jq + optional phi-venv + all runtime artifacts incl. log/headers.log, sessions, journal, lessons, creds) and the `larry` PATH shim. DRY-RUN by default; --yes to delete, --keep-data to preserve user data. Removes ONLY what the installer created (shim removed only if it carries our auto-gen header; shell rc / Cloverleaf sites / $HCIROOT never touched). Stops running PHI sidecar / tunnel via their own pidfiles. Shipped by the installer + manifest-synced; dispatched early like `larry tools` so it works offline. 2. --no-api (env LARRY_NO_API=1) — deterministic-only mode making ZERO LLM API calls (zero cost). REPL + all local/deterministic commands still work; a free-text prompt is routed to the matching `larry tools <name>` instead of the model. No API key required (first-run auth prompt skipped). call_api / call_api_stream hard-refuse as defense in depth. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
207 lines
11 KiB
Bash
Executable File
207 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# uninstall-larry.sh — cleanly remove everything install-larry.sh put down.
|
|
#
|
|
# Reverses the installer EXACTLY: it removes only what Larry-Anywhere created
|
|
# (its install dir, the `larry` PATH shim, the optional PHI venv, the jq
|
|
# fallback binary, and all runtime artifacts under $LARRY_HOME) and NOTHING the
|
|
# installer didn't create. It does NOT touch your shell rc, your Cloverleaf
|
|
# sites, your $HCIROOT, or any file outside the install footprint.
|
|
#
|
|
# Default is a DRY-RUN preview. You must pass --yes (or confirm at the prompt)
|
|
# to actually delete. Every path removed is printed.
|
|
#
|
|
# Usage:
|
|
# uninstall-larry.sh # dry-run: list exactly what WOULD be removed
|
|
# uninstall-larry.sh --dry-run # (explicit) same as above
|
|
# uninstall-larry.sh --yes # remove without the interactive prompt
|
|
# uninstall-larry.sh --keep-data # remove program files but KEEP user data
|
|
# # (sessions/, journal/, lessons/,
|
|
# # knowledge/, sanitize/, log/, creds)
|
|
# uninstall-larry.sh --help
|
|
#
|
|
# Also reachable as a subcommand: larry uninstall [--dry-run|--yes|--keep-data]
|
|
#
|
|
# Env vars (mirror the installer so it targets the SAME footprint):
|
|
# LARRY_HOME install location (default: $HOME/.larry)
|
|
# LARRY_BIN_DIR where the `larry` shim was symlinked (default: $HOME/bin)
|
|
set -eu
|
|
|
|
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
|
|
LARRY_BIN_DIR="${LARRY_BIN_DIR:-$HOME/bin}"
|
|
|
|
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_GREEN=$'\033[32m'
|
|
C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'; C_CYAN=$'\033[36m'; C_DIM=$'\033[2m'
|
|
|
|
say() { printf '%s%suninstall-larry>%s %s\n' "$C_CYAN" "$C_BOLD" "$C_RESET" "$*"; }
|
|
ok() { printf ' %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
|
|
warn() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; }
|
|
die() { printf '%serror:%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; exit 1; }
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Args
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
DRY_RUN=1 # default: preview only
|
|
ASSUME_YES=0
|
|
KEEP_DATA=0
|
|
for arg in "${@:-}"; do
|
|
case "$arg" in
|
|
--yes|-y) DRY_RUN=0; ASSUME_YES=1 ;;
|
|
--dry-run|-n) DRY_RUN=1 ;;
|
|
--keep-data) KEEP_DATA=1 ;;
|
|
--help|-h) sed -n '2,38p' "$0"; exit 0 ;;
|
|
"") : ;;
|
|
*) die "unknown flag: $arg (try --help)" ;;
|
|
esac
|
|
done
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Stop background processes Larry may have started, BEFORE deleting their files.
|
|
# Both PID files live under $LARRY_HOME. Best-effort: a stale/dead PID is fine.
|
|
# We only kill a PID we can read from Larry's own pidfile — never a guessed one.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
stop_bg_proc() {
|
|
# $1 = human label, $2 = pidfile path
|
|
local label="$1" pidfile="$2" pid=""
|
|
[ -f "$pidfile" ] || return 0
|
|
pid="$(tr -d '[:space:]' < "$pidfile" 2>/dev/null || echo "")"
|
|
case "$pid" in
|
|
''|*[!0-9]*) return 0 ;; # empty or non-numeric → nothing safe to do
|
|
esac
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
if [ "$DRY_RUN" = "1" ]; then
|
|
printf ' %s•%s would stop %s (PID %s)\n' "$C_YELLOW" "$C_RESET" "$label" "$pid"
|
|
else
|
|
kill "$pid" 2>/dev/null && ok "stopped $label (PID $pid)" || warn "could not stop $label (PID $pid) — kill it manually"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Build the removal list. We separate PROGRAM artifacts (always removed) from
|
|
# USER DATA (removed unless --keep-data). Everything here was created by the
|
|
# installer or by Larry at runtime — we never list anything else.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
PROGRAM_TARGETS=() # files/dirs that ARE the install footprint
|
|
DATA_TARGETS=() # user-generated data under $LARRY_HOME
|
|
|
|
# The shim the installer drops onto PATH. We only remove it if it is OUR shim
|
|
# (auto-generated header), so we never delete an unrelated `larry` someone else
|
|
# put there.
|
|
SHIM="$LARRY_BIN_DIR/larry"
|
|
if [ -f "$SHIM" ] && grep -q 'Auto-generated by install-larry.sh' "$SHIM" 2>/dev/null; then
|
|
PROGRAM_TARGETS+=("$SHIM")
|
|
elif [ -e "$SHIM" ]; then
|
|
warn "$SHIM exists but is NOT our auto-generated shim — leaving it untouched."
|
|
fi
|
|
|
|
# The entire install dir is ours. Rather than enumerate every dotfile, we list
|
|
# $LARRY_HOME itself for the full uninstall. With --keep-data we instead remove
|
|
# the program files inside it and preserve the user-data subtrees.
|
|
if [ -d "$LARRY_HOME" ]; then
|
|
if [ "$KEEP_DATA" = "0" ]; then
|
|
PROGRAM_TARGETS+=("$LARRY_HOME")
|
|
else
|
|
# Program files (shipped bundle + caches + state markers + the jq fallback
|
|
# + the optional PHI venv). Anything not in this list under $LARRY_HOME is
|
|
# treated as user data and preserved.
|
|
for p in \
|
|
larry.sh larry-tunnel.sh larry-rollback.sh larry-auth.sh uninstall-larry.sh \
|
|
install-larry.sh VERSION MANUAL.md MANIFEST CHANGELOG.md README.md \
|
|
inbound-systems.tsv agents lib bin phi-venv \
|
|
.last-sync-version .origin .oauth-optin-warned \
|
|
.phi-notice-shown .b64-py3-notice-shown \
|
|
.last-curl-rc .last-curl-stderr .last-curl-headers \
|
|
.last-stream-headers .last-stream-curlerr \
|
|
.phi-sidecar.pid tunnel.pid tunnel.url tunnel.log .history; do
|
|
[ -e "$LARRY_HOME/$p" ] && PROGRAM_TARGETS+=("$LARRY_HOME/$p")
|
|
done
|
|
# User data preserved under --keep-data (listed for transparency).
|
|
for d in sessions journal lessons knowledge sanitize regression log \
|
|
.api-key .env .oauth.json .ssh-creds .ssh-sockets .ssh-hosts.tsv \
|
|
known_hosts .headers-sync-offset .headers-sync-state; do
|
|
[ -e "$LARRY_HOME/$d" ] && DATA_TARGETS+=("$LARRY_HOME/$d")
|
|
done
|
|
fi
|
|
fi
|
|
|
|
if [ "${#PROGRAM_TARGETS[@]}" -eq 0 ]; then
|
|
say "nothing to remove — no Larry-Anywhere install footprint found."
|
|
say " (looked for LARRY_HOME=$LARRY_HOME and shim $SHIM)"
|
|
exit 0
|
|
fi
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Preview
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
say "Larry-Anywhere uninstall — install footprint to remove:"
|
|
echo ""
|
|
printf '%sProgram files (always removed):%s\n' "$C_BOLD" "$C_RESET"
|
|
for t in "${PROGRAM_TARGETS[@]}"; do
|
|
if [ -d "$t" ]; then printf ' %s(dir) %s%s\n' "$C_DIM" "$t" "$C_RESET"
|
|
else printf ' %s\n' "$t"; fi
|
|
done
|
|
if [ "$KEEP_DATA" = "1" ]; then
|
|
echo ""
|
|
printf '%sUser data PRESERVED (--keep-data):%s\n' "$C_BOLD" "$C_RESET"
|
|
if [ "${#DATA_TARGETS[@]}" -eq 0 ]; then
|
|
printf ' %s(none found)%s\n' "$C_DIM" "$C_RESET"
|
|
else
|
|
for d in "${DATA_TARGETS[@]}"; do printf ' %skept%s %s\n' "$C_GREEN" "$C_RESET" "$d"; done
|
|
fi
|
|
fi
|
|
echo ""
|
|
say "NOT touched: your shell rc (any PATH export you added stays — remove it by hand),"
|
|
say " your Cloverleaf sites / \$HCIROOT, and anything outside the list above."
|
|
echo ""
|
|
|
|
# Stop background procs (preview prints "would stop …"; real run kills them).
|
|
stop_bg_proc "PHI Presidio sidecar" "$LARRY_HOME/.phi-sidecar.pid"
|
|
stop_bg_proc "reverse SSH tunnel" "$LARRY_HOME/tunnel.pid"
|
|
echo ""
|
|
|
|
if [ "$DRY_RUN" = "1" ]; then
|
|
say "${C_BOLD}DRY-RUN${C_RESET} — nothing was removed. Re-run with ${C_CYAN}--yes${C_RESET} to delete the above."
|
|
exit 0
|
|
fi
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Confirm (skipped with --yes... but --yes already set ASSUME_YES; this is the
|
|
# belt-and-suspenders path if someone sets DRY_RUN=0 by other means).
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
if [ "$ASSUME_YES" != "1" ]; then
|
|
printf '%sProceed with removal? This cannot be undone. [y/N]: %s' "$C_YELLOW" "$C_RESET"
|
|
ans=""
|
|
if [ -t 0 ]; then read -r ans </dev/tty || ans=""; else read -r ans || ans=""; fi
|
|
case "$ans" in
|
|
y|Y|yes|YES) : ;;
|
|
*) say "aborted — nothing removed."; exit 0 ;;
|
|
esac
|
|
fi
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Remove. Print every path as it goes. rm -rf is scoped strictly to the list we
|
|
# built above — we never expand globs or touch a parent dir.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
removed=0
|
|
for t in "${PROGRAM_TARGETS[@]}"; do
|
|
if rm -rf "$t" 2>/dev/null; then
|
|
ok "removed $t"
|
|
removed=$((removed+1))
|
|
else
|
|
warn "could not remove $t (check permissions; remove by hand)"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
say "removed $removed item(s)."
|
|
if [ "$KEEP_DATA" = "1" ] && [ "${#DATA_TARGETS[@]}" -gt 0 ]; then
|
|
say "your data is preserved under $LARRY_HOME — delete it manually if you want a full wipe."
|
|
fi
|
|
# Reminder: the installer never EDITS your shell rc; it only WARNS you to add a
|
|
# PATH export. If you added one, it's still there — this is the one manual step.
|
|
case ":$PATH:" in
|
|
*":$LARRY_BIN_DIR:"*)
|
|
say "reminder: you may have added 'export PATH=\"$LARRY_BIN_DIR:\$PATH\"' to your shell rc. Remove that line by hand if $LARRY_BIN_DIR is now empty." ;;
|
|
esac
|
|
say "uninstall complete."
|