From 121f5866bef9f980d559429d3d7e45b19520065b Mon Sep 17 00:00:00 2001 From: ZanzyTHEbar Date: Sat, 21 Mar 2026 15:25:32 +0000 Subject: [PATCH 1/6] fix: keep AppImage terminals resolving to the cursor shim Cursor-launched shells can put AppImage runtime mounts ahead of ~/.local/bin, which bypasses the shim and breaks agent and installer flows. Manage shell PATH setup and tighten shim detection so installs remain idempotent regardless of install order. --- README.md | 28 +++++--- cursor.sh | 6 ++ install.sh | 9 ++- lib.sh | 65 ++++++++++++++++- scripts/ensure-shell-path.sh | 132 +++++++++++++++++++++++++++++++++++ scripts/ensure-shim.sh | 10 ++- shell-path.sh | 23 ++++++ shim.sh | 98 +++++++++++++++++++++++--- uninstall.sh | 10 ++- 9 files changed, 355 insertions(+), 26 deletions(-) create mode 100644 scripts/ensure-shell-path.sh create mode 100644 shell-path.sh diff --git a/README.md b/README.md index 367d9fe..bc40b7e 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,8 @@ The one‑liner script will: 2. Download `lib.sh` to `~/.local/share/cursor-installer/lib.sh` 3. Make the script executable 4. Install a `cursor` shim at `~/.local/bin/cursor` (see [The `cursor` Shim](#the-cursor-shim)) -5. Download and install the latest version of Cursor +5. Install a managed shell startup hook for supported shells so `~/.local/bin` stays ahead of transient AppImage runtime paths +6. Download and install the latest version of Cursor **Note:** If you're installing via the piped bash method and don't have FUSE2 installed, the script will warn you but continue. You'll need to either: @@ -125,7 +126,8 @@ The uninstall script will: 1. Remove the `cursor-installer` script from `~/.local/bin/` 2. Remove the shared `lib.sh` from `~/.local/share/cursor-installer/` 3. Remove the Cursor AppImage -4. Ask if you want to remove the Cursor configuration files +4. Remove the managed shell PATH hook from supported shell startup files +5. Ask if you want to remove the Cursor configuration files **Note:** The `cursor` shim at `~/.local/bin/cursor` is not removed by the uninstall script. See [Removing the Shim](#removing-the-shim) for manual cleanup. @@ -211,12 +213,19 @@ The shim bridges that gap. It installs a lightweight script at `~/.local/bin/cur When you type `cursor`, the shim (`~/.local/bin/cursor`) follows a short resolution chain: -1. **Real Cursor binary found in PATH?** -- Forward all arguments to it (e.g. Cursor's official `cursor` CLI). -2. **`cursor agent` subcommand?** -- Delegate to `~/.local/bin/agent` if it exists. -3. **`cursor-installer` found?** -- Delegate to the installer CLI so commands like `cursor --update` still work. -4. **Nothing found** -- Print a helpful error with install instructions. +1. **`cursor agent` subcommand?** -- Delegate to `~/.local/bin/agent` if it exists. +2. **Installer-only flag?** -- Delegate to `cursor-installer` for commands like `cursor --update`, `cursor --check`, or `cursor --extract`. +3. **Stable Cursor binary found in PATH?** -- Forward all other arguments to it (e.g. Cursor's official `cursor` CLI). +4. **`cursor-installer` found?** -- Delegate to the installer CLI as a general fallback. +5. **Nothing found** -- Print a helpful error with install instructions. -The shim never hides a real Cursor binary; it only acts as a fallback. +The shim does not override a stable Cursor CLI, but it deliberately ignores transient AppImage mount paths under `/tmp/.mount_*` and normalizes duplicate path aliases so it cannot recurse back into itself. + +### AppImage Terminals + +When Cursor is launched from an AppImage, terminals opened inside Cursor may inherit a `PATH` where Cursor's transient runtime mount (`/tmp/.mount_*`) appears before `~/.local/bin`. That can bypass the shim entirely. + +To keep `cursor` resolving to the shim in supported shells, the installer manages a small startup hook that prepends `~/.local/bin` in interactive `bash` and `zsh` sessions. This keeps the shim available while still allowing it to delegate to a real Cursor CLI when appropriate. ### How It Works @@ -241,6 +250,7 @@ The shim is synced automatically during normal installer operations: - **`install.sh`** -- Copies `shim.sh` and `ensure-shim.sh` into `~/.local/share/cursor-installer/`, then runs `ensure-shim.sh`. - **`cursor-installer --update`** -- Re-downloads the latest shim assets from GitHub, then re-runs `ensure-shim.sh`. - **`cursor-installer` (install paths)** -- Runs `ensure-shim.sh` before each install to keep the shim current. +- **Shell PATH setup** -- Syncs `shell-path.sh` and `ensure-shell-path.sh`, then ensures supported shell startup files source the PATH helper. ### File Locations @@ -249,6 +259,8 @@ The shim is synced automatically during normal installer operations: | `~/.local/bin/cursor` | The shim (what you invoke). | | `~/.local/share/cursor-installer/shim.sh` | Cached copy of the shim source. | | `~/.local/share/cursor-installer/ensure-shim.sh` | Cached copy of the installer helper. | +| `~/.local/share/cursor-installer/shell-path.sh` | Shell snippet that prepends `~/.local/bin`. | +| `~/.local/share/cursor-installer/ensure-shell-path.sh` | Helper that updates supported shell startup files. | ### Removing the Shim @@ -264,7 +276,7 @@ If you only want to disable the shim without uninstalling the rest of the projec ## Note -If you encounter a warning that `~/.local/bin` is not in your PATH, you can add it by running: +If you encounter a warning that `~/.local/bin` is not in your PATH, or if `cursor` resolves to Cursor's transient AppImage runtime instead of the shim, prepend it by running: ```bash export PATH="$HOME/.local/bin:$PATH" diff --git a/cursor.sh b/cursor.sh index 3c7c12e..8ffdac9 100755 --- a/cursor.sh +++ b/cursor.sh @@ -290,6 +290,7 @@ EOF function install_cursor_extracted() { run_ensure_shim + run_ensure_shell_path local install_dir="$1" local release_track=${2:-stable} local temp_file @@ -440,6 +441,7 @@ function install_cursor_extracted() { function install_cursor() { run_ensure_shim + run_ensure_shell_path local install_dir="$1" local release_track=${2:-stable} # Default to stable if not specified @@ -666,6 +668,10 @@ EOF function update_cursor() { log_step "Updating Cursor..." refresh_shim_assets + refresh_shell_path_assets + run_ensure_shim + run_ensure_shell_path + warn_if_cursor_shadowed_by_appimage_runtime local current_appimage current_appimage=$(find_cursor_appimage || true) local install_dir diff --git a/install.sh b/install.sh index 707b5d0..3513cd1 100755 --- a/install.sh +++ b/install.sh @@ -94,8 +94,12 @@ chmod +x "$CLI_PATH" log_ok "Cursor installer script has been placed in $CLI_PATH" -log_step "Ensuring cursor shim..." -LOCAL_SHIM_PATH="$SCRIPT_DIR/shim.sh" LOCAL_SHIM_HELPER_PATH="$SCRIPT_DIR/scripts/ensure-shim.sh" sync_shim_assets && run_ensure_shim || log_warn "Shim update skipped or failed; continuing." +log_step "Ensuring cursor shim and shell PATH setup..." +LOCAL_SHIM_PATH="$SCRIPT_DIR/shim.sh" +LOCAL_SHIM_HELPER_PATH="$SCRIPT_DIR/scripts/ensure-shim.sh" +LOCAL_SHELL_PATH_SCRIPT="$SCRIPT_DIR/shell-path.sh" +LOCAL_SHELL_PATH_HELPER_PATH="$SCRIPT_DIR/scripts/ensure-shell-path.sh" +sync_shim_assets && sync_shell_path_assets && run_ensure_shim && run_ensure_shell_path || log_warn "Shim or shell PATH setup skipped or failed; continuing." # Check if ~/.local/bin is in PATH if [[ ":$PATH:" != *":$LOCAL_BIN:"* ]]; then @@ -103,6 +107,7 @@ if [[ ":$PATH:" != *":$LOCAL_BIN:"* ]]; then log_info "To add it, run this or add it to your shell profile:" log_info "export PATH=\"\$HOME/.local/bin:\$PATH\"" fi +warn_if_cursor_shadowed_by_appimage_runtime # Run cursor --update to download and install Cursor log_step "Downloading and installing Cursor ($INSTALL_MODE mode) from ${REPO_OWNER}/${REPO_NAME}@${REPO_BRANCH}..." diff --git a/lib.sh b/lib.sh index 81aed43..55abee4 100644 --- a/lib.sh +++ b/lib.sh @@ -114,8 +114,12 @@ BASE_RAW_URL="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${REP SHIM_TARGET="${SHIM_TARGET:-$HOME/.local/bin/cursor}" SHARED_SHIM="${LIB_DIR}/shim.sh" SHIM_HELPER="${LIB_DIR}/ensure-shim.sh" +SHELL_PATH_SCRIPT="${LIB_DIR}/shell-path.sh" +SHELL_PATH_HELPER="${LIB_DIR}/ensure-shell-path.sh" SHIM_URL="${BASE_RAW_URL}/shim.sh" SHIM_HELPER_URL="${BASE_RAW_URL}/scripts/ensure-shim.sh" +SHELL_PATH_SCRIPT_URL="${BASE_RAW_URL}/shell-path.sh" +SHELL_PATH_HELPER_URL="${BASE_RAW_URL}/scripts/ensure-shell-path.sh" LIB_URL="${BASE_RAW_URL}/lib.sh" CURSOR_SCRIPT_URL="${BASE_RAW_URL}/cursor.sh" @@ -137,6 +141,22 @@ function sync_shim_assets() { return 0 } +function sync_shell_path_assets() { + mkdir -p "$LIB_DIR" + if [ -n "${LOCAL_SHELL_PATH_SCRIPT:-}" ] && [ -f "$LOCAL_SHELL_PATH_SCRIPT" ]; then + cp "$LOCAL_SHELL_PATH_SCRIPT" "$SHELL_PATH_SCRIPT" + elif [ ! -f "$SHELL_PATH_SCRIPT" ]; then + curl -fsSL "$SHELL_PATH_SCRIPT_URL" -o "$SHELL_PATH_SCRIPT" || { log_warn "Failed to download shell-path.sh"; return 1; } + fi + if [ -n "${LOCAL_SHELL_PATH_HELPER_PATH:-}" ] && [ -f "$LOCAL_SHELL_PATH_HELPER_PATH" ]; then + cp "$LOCAL_SHELL_PATH_HELPER_PATH" "$SHELL_PATH_HELPER" + elif [ ! -f "$SHELL_PATH_HELPER" ]; then + curl -fsSL "$SHELL_PATH_HELPER_URL" -o "$SHELL_PATH_HELPER" || { log_warn "Failed to download ensure-shell-path.sh"; return 1; } + fi + chmod +x "$SHELL_PATH_HELPER" "$SHELL_PATH_SCRIPT" 2>/dev/null || true + return 0 +} + # Refresh shim assets from GitHub (used on cursor-installer --update). function refresh_shim_assets() { log_step "Refreshing cursor shim assets..." @@ -152,11 +172,52 @@ function refresh_shim_assets() { chmod +x "$SHIM_HELPER" "$SHARED_SHIM" 2>/dev/null || true } +function refresh_shell_path_assets() { + log_step "Refreshing shell PATH assets..." + mkdir -p "$LIB_DIR" + if ! curl -fsSL "$SHELL_PATH_SCRIPT_URL" -o "$SHELL_PATH_SCRIPT"; then + log_warn "Failed to download shell-path.sh; continuing." + return 0 + fi + if ! curl -fsSL "$SHELL_PATH_HELPER_URL" -o "$SHELL_PATH_HELPER"; then + log_warn "Failed to download ensure-shell-path.sh; continuing." + return 0 + fi + chmod +x "$SHELL_PATH_HELPER" "$SHELL_PATH_SCRIPT" 2>/dev/null || true +} + # Run ensure-shim.sh with canonical SOURCE_SHIM and TARGET_SHIM. function run_ensure_shim() { - if [ ! -x "$SHIM_HELPER" ] && [ ! -f "$SHIM_HELPER" ]; then + if [ ! -f "$SHIM_HELPER" ]; then log_info "Shim helper not found; skipping shim update." return 0 fi - SOURCE_SHIM="$SHARED_SHIM" TARGET_SHIM="$SHIM_TARGET" "$SHIM_HELPER" || { log_warn "Shim update failed; continuing."; return 0; } + SOURCE_SHIM="$SHARED_SHIM" TARGET_SHIM="$SHIM_TARGET" sh "$SHIM_HELPER" || { log_warn "Shim update failed; continuing."; return 0; } +} + +function run_ensure_shell_path() { + if [ ! -f "$SHELL_PATH_HELPER" ] || [ ! -f "$SHELL_PATH_SCRIPT" ]; then + log_info "Shell PATH helper not found; skipping shell PATH setup." + return 0 + fi + SHELL_PATH_SCRIPT="$SHELL_PATH_SCRIPT" sh "$SHELL_PATH_HELPER" || { log_warn "Shell PATH setup failed; continuing."; return 0; } +} + +function run_remove_shell_path() { + if [ ! -f "$SHELL_PATH_HELPER" ]; then + return 0 + fi + SHELL_PATH_SCRIPT="$SHELL_PATH_SCRIPT" sh "$SHELL_PATH_HELPER" --remove || { log_warn "Shell PATH cleanup failed; continuing."; return 0; } +} + +function warn_if_cursor_shadowed_by_appimage_runtime() { + local resolved_cursor + resolved_cursor=$(command -v cursor 2>/dev/null || true) + + case "$resolved_cursor" in + /tmp/.mount_*) + log_warn "The current shell resolves 'cursor' to Cursor's AppImage runtime path." + log_info "Open a new terminal or source your shell startup file so ~/.local/bin takes precedence." + ;; + esac } diff --git a/scripts/ensure-shell-path.sh b/scripts/ensure-shell-path.sh new file mode 100644 index 0000000..388af6d --- /dev/null +++ b/scripts/ensure-shell-path.sh @@ -0,0 +1,132 @@ +#!/bin/sh +# Ensure supported interactive shells source cursor-installer's PATH helper. +set -eu + +ACTION="${1:-ensure}" +LIB_DIR="${HOME}/.local/share/cursor-installer" +SHELL_PATH_SCRIPT="${SHELL_PATH_SCRIPT:-$LIB_DIR/shell-path.sh}" +START_MARKER="# >>> cursor-installer path >>>" +END_MARKER="# <<< cursor-installer path <<<" + +build_source_block() { + cat <&2 + return 1 + ;; + esac +} + +strip_managed_block() { + file="$1" + tmp="$2" + + if [ -f "$file" ]; then + awk -v start="$START_MARKER" -v end="$END_MARKER" ' + $0 == start { skip = 1; next } + skip && $0 == end { skip = 0; next } + !skip { print } + ' "$file" > "$tmp" + else + : > "$tmp" + fi +} + +ensure_block() { + file="$1" + tmp=$(mktemp) + mkdir -p "$(dirname "$file")" + strip_managed_block "$file" "$tmp" + + if [ -s "$tmp" ]; then + printf '\n' >> "$tmp" + fi + + build_source_block >> "$tmp" + + if [ -f "$file" ] && cmp -s "$tmp" "$file"; then + rm -f "$tmp" + echo "Shell PATH setup already present in $file" + return 0 + fi + + mv "$tmp" "$file" + echo "Ensured shell PATH setup in $file" +} + +remove_block() { + file="$1" + [ -f "$file" ] || return 0 + + tmp=$(mktemp) + strip_managed_block "$file" "$tmp" + + if cmp -s "$tmp" "$file"; then + rm -f "$tmp" + return 0 + fi + + mv "$tmp" "$file" + echo "Removed shell PATH setup from $file" +} + +if [ "$ACTION" = "ensure" ] && [ ! -f "$SHELL_PATH_SCRIPT" ]; then + echo "Error: shell-path.sh source not found at $SHELL_PATH_SCRIPT" >&2 + exit 1 +fi + +if ! target_files=$(print_target_files); then + exit 0 +fi + +printf '%s\n' "$target_files" | while IFS= read -r file; do + [ -n "$file" ] || continue + + case "$ACTION" in + ensure) + ensure_block "$file" + ;; + --remove|remove) + remove_block "$file" + ;; + *) + echo "Unknown action: $ACTION" >&2 + exit 1 + ;; + esac +done diff --git a/scripts/ensure-shim.sh b/scripts/ensure-shim.sh index 9d805f7..fc1ebf6 100644 --- a/scripts/ensure-shim.sh +++ b/scripts/ensure-shim.sh @@ -5,6 +5,7 @@ set -eu TARGET_SHIM="${TARGET_SHIM:-$HOME/.local/bin/cursor}" SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) LIB_DIR="${HOME}/.local/share/cursor-installer" +SHIM_MARKER="cursor-linux-installer-shim" SOURCE_SHIM="${SOURCE_SHIM:-}" if [ -z "$SOURCE_SHIM" ]; then @@ -36,12 +37,17 @@ is_shim() { return 1 ;; esac - if grep -q "Find cursor executable in PATH" "$file" 2>/dev/null; then + + if grep -Fq "$SHIM_MARKER" "$file" 2>/dev/null; then return 0 fi - if grep -q "cursor-installer" "$file" 2>/dev/null; then + + if grep -Fq "Find cursor executable in PATH" "$file" 2>/dev/null && + grep -Fq 'AGENT_BIN="$HOME/.local/bin/agent"' "$file" 2>/dev/null && + grep -Fq 'Install/update with: cursor-installer --update [stable|latest]' "$file" 2>/dev/null; then return 0 fi + return 1 } diff --git a/shell-path.sh b/shell-path.sh new file mode 100644 index 0000000..e8ef79c --- /dev/null +++ b/shell-path.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# cursor-linux-installer-path + +cursor_installer_local_bin="$HOME/.local/bin" + +if [ -d "$cursor_installer_local_bin" ]; then + cursor_installer_filtered_path=$( + printf '%s' "${PATH:-}" | + awk -v RS=: -v ORS=: -v skip="$cursor_installer_local_bin" '$0 != skip { print }' | + sed 's/:$//' + ) + + if [ -n "$cursor_installer_filtered_path" ]; then + PATH="$cursor_installer_local_bin:$cursor_installer_filtered_path" + else + PATH="$cursor_installer_local_bin" + fi + + export PATH +fi + +unset cursor_installer_local_bin +unset cursor_installer_filtered_path diff --git a/shim.sh b/shim.sh index 001bf49..af82171 100644 --- a/shim.sh +++ b/shim.sh @@ -1,33 +1,95 @@ #!/bin/sh set -eu +# cursor-linux-installer-shim # Find cursor executable in PATH, excluding the current shim +canonicalize_path() { + path="$1" + + if command -v realpath >/dev/null 2>&1; then + realpath "$path" 2>/dev/null && return 0 + fi + + if command -v readlink >/dev/null 2>&1; then + readlink -f "$path" 2>/dev/null && return 0 + fi + + case "$path" in + */*) + dir_part=${path%/*} + base_part=${path##*/} + ;; + *) + dir_part=. + base_part=$path + ;; + esac + + old_pwd=$(pwd) + if cd "$dir_part" 2>/dev/null; then + resolved_dir=$(pwd -P) + cd "$old_pwd" || exit 1 + printf '%s/%s\n' "$resolved_dir" "$base_part" + return 0 + fi + + cd "$old_pwd" || exit 1 + printf '%s\n' "$path" +} + +same_path() { + left=$(canonicalize_path "$1" || printf '%s\n' "$1") + right=$(canonicalize_path "$2" || printf '%s\n' "$2") + [ "$left" = "$right" ] +} + +is_ignored_cursor_path() { + case "$1" in + # Ignore transient AppImage runtime mounts; they are not stable CLI installs + # and can shadow the shim inside terminals launched from Cursor itself. + /tmp/.mount_*) + return 0 + ;; + esac + + return 1 +} + +SHIM_PATH=$(canonicalize_path "$HOME/.local/bin/cursor" || printf '%s\n' "$HOME/.local/bin/cursor") +case "${0:-}" in + */*) + SHIM_PATH=$(canonicalize_path "$0" || printf '%s\n' "$0") + ;; +esac + find_cursor() { old_IFS="$IFS" IFS=: for dir in $PATH; do [ -n "$dir" ] || continue cursor_path="$dir/cursor" - if [ "$cursor_path" != "$HOME/.local/bin/cursor" ] && [ -x "$cursor_path" ]; then - IFS="$old_IFS" - echo "$cursor_path" - return 0 + [ -x "$cursor_path" ] || continue + + if is_ignored_cursor_path "$cursor_path"; then + continue fi + + if same_path "$cursor_path" "$SHIM_PATH"; then + continue + fi + + IFS="$old_IFS" + echo "$cursor_path" + return 0 done IFS="$old_IFS" return 1 } -OTHER_CURSOR=$(find_cursor || true) +first_arg="${1:-}" CURSOR_INSTALLER=$(command -v cursor-installer 2>/dev/null || true) AGENT_BIN="$HOME/.local/bin/agent" -if [ -n "${OTHER_CURSOR:-}" ]; then - exec "$OTHER_CURSOR" "$@" -fi - -first_arg="${1:-}" - if [ "$first_arg" = "agent" ]; then if [ -x "$AGENT_BIN" ]; then exec "$AGENT_BIN" "$@" @@ -36,6 +98,20 @@ if [ "$first_arg" = "agent" ]; then exit 1 fi +case "$first_arg" in + --update|-u|--check|-c|--extract|--no-fuse|--reinstall-desktop) + if [ -n "${CURSOR_INSTALLER:-}" ]; then + exec "$CURSOR_INSTALLER" "$@" + fi + ;; +esac + +OTHER_CURSOR=$(find_cursor || true) + +if [ -n "${OTHER_CURSOR:-}" ]; then + exec "$OTHER_CURSOR" "$@" +fi + if [ -n "${CURSOR_INSTALLER:-}" ]; then exec "$CURSOR_INSTALLER" "$@" fi diff --git a/uninstall.sh b/uninstall.sh index 859c21b..9213a73 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -48,7 +48,15 @@ fi log_step "Removing cursor-installer script..." safe_remove "$CLI_PATH" "cursor-installer script" -# Remove shared lib (installed by installer) +# Remove managed shell PATH setup before deleting helper assets +log_step "Removing managed shell PATH setup..." +run_remove_shell_path + +# Remove shared support assets (installed by installer) +safe_remove "$SHARED_SHIM" "cursor shim source" +safe_remove "$SHIM_HELPER" "cursor shim helper" +safe_remove "$SHELL_PATH_SCRIPT" "shell PATH helper script" +safe_remove "$SHELL_PATH_HELPER" "shell PATH helper" safe_remove "$SHARED_LIB" "cursor-installer lib" if [ -d "$LIB_DIR" ] && [ -z "$(ls -A "$LIB_DIR")" ]; then rmdir "$LIB_DIR" 2>/dev/null || true From 89455866f2f29d2691fcbf032a87789fcf8278de Mon Sep 17 00:00:00 2001 From: ZanzyTHEbar Date: Sat, 21 Mar 2026 16:18:38 +0000 Subject: [PATCH 2/6] fix: preserve symlinked shell rc files Update the PATH hook helper in place when an rc file already exists so dotfile-managed symlinks keep pointing at their original targets during install and removal. --- scripts/ensure-shell-path.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/ensure-shell-path.sh b/scripts/ensure-shell-path.sh index 388af6d..0d03d38 100644 --- a/scripts/ensure-shell-path.sh +++ b/scripts/ensure-shell-path.sh @@ -67,6 +67,19 @@ strip_managed_block() { fi } +write_updated_file() { + tmp="$1" + file="$2" + + if [ -e "$file" ] || [ -L "$file" ]; then + cat "$tmp" > "$file" + rm -f "$tmp" + return 0 + fi + + mv "$tmp" "$file" +} + ensure_block() { file="$1" tmp=$(mktemp) @@ -85,7 +98,7 @@ ensure_block() { return 0 fi - mv "$tmp" "$file" + write_updated_file "$tmp" "$file" echo "Ensured shell PATH setup in $file" } @@ -101,7 +114,7 @@ remove_block() { return 0 fi - mv "$tmp" "$file" + write_updated_file "$tmp" "$file" echo "Removed shell PATH setup from $file" } From 70840f4aa8a899519ac8e5e3da0a056e0edac3ea Mon Sep 17 00:00:00 2001 From: ZanzyTHEbar Date: Sat, 21 Mar 2026 17:14:50 +0000 Subject: [PATCH 3/6] fix: strip the routed agent subcommand The shim should route `cursor agent ...` into the agent binary without forwarding the `agent` dispatcher token itself. Shift the subcommand before exec so the agent CLI receives only its own arguments. --- shim.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/shim.sh b/shim.sh index af82171..f9c39b8 100644 --- a/shim.sh +++ b/shim.sh @@ -92,6 +92,7 @@ AGENT_BIN="$HOME/.local/bin/agent" if [ "$first_arg" = "agent" ]; then if [ -x "$AGENT_BIN" ]; then + shift exec "$AGENT_BIN" "$@" fi echo "Error: Cursor agent not found at $AGENT_BIN" 1>&2 From 4fc839499705934b812eff6586e88db012e433b2 Mon Sep 17 00:00:00 2001 From: ZanzyTHEbar Date: Sat, 21 Mar 2026 17:15:01 +0000 Subject: [PATCH 4/6] fix: keep shell PATH setup idempotent Repeated installs should not rewrite shell rc files with extra blank lines or lose track of the intended destination variables. Trim trailing separator whitespace before re-appending the managed block and avoid leaking helper-local variable names across functions. --- scripts/ensure-shell-path.sh | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/scripts/ensure-shell-path.sh b/scripts/ensure-shell-path.sh index 0d03d38..aacafba 100644 --- a/scripts/ensure-shell-path.sh +++ b/scripts/ensure-shell-path.sh @@ -67,17 +67,37 @@ strip_managed_block() { fi } +trim_trailing_blank_lines() { + trim_file="$1" + trimmed_file=$(mktemp) + + awk ' + { lines[NR] = $0 } + END { + last = NR + while (last > 0 && lines[last] ~ /^[[:space:]]*$/) { + last-- + } + for (i = 1; i <= last; i++) { + print lines[i] + } + } + ' "$trim_file" > "$trimmed_file" + + mv "$trimmed_file" "$trim_file" +} + write_updated_file() { - tmp="$1" - file="$2" + source_tmp="$1" + destination_file="$2" - if [ -e "$file" ] || [ -L "$file" ]; then - cat "$tmp" > "$file" - rm -f "$tmp" + if [ -e "$destination_file" ] || [ -L "$destination_file" ]; then + cat "$source_tmp" > "$destination_file" + rm -f "$source_tmp" return 0 fi - mv "$tmp" "$file" + mv "$source_tmp" "$destination_file" } ensure_block() { @@ -85,6 +105,7 @@ ensure_block() { tmp=$(mktemp) mkdir -p "$(dirname "$file")" strip_managed_block "$file" "$tmp" + trim_trailing_blank_lines "$tmp" if [ -s "$tmp" ]; then printf '\n' >> "$tmp" From 4383377535ebf89fc3d6a2b3f832d60c8da440bc Mon Sep 17 00:00:00 2001 From: ZanzyTHEbar Date: Tue, 31 Mar 2026 16:56:51 +0100 Subject: [PATCH 5/6] fix: prefer local installer assets during updates Persist the source checkout for local installs and reuse those local shim and shell-path assets on later updates so branch testing doesn't silently downgrade back to main. --- cursor.sh | 15 +++++++++- install.sh | 12 ++++++++ lib.sh | 80 +++++++++++++++++++++++++++++++++++++++++++++++----- uninstall.sh | 1 + 4 files changed, 100 insertions(+), 8 deletions(-) diff --git a/cursor.sh b/cursor.sh index 8ffdac9..5b483e8 100755 --- a/cursor.sh +++ b/cursor.sh @@ -6,11 +6,24 @@ SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) LIB_DIR="$HOME/.local/share/cursor-installer" LIB_PATH="$SCRIPT_DIR/lib.sh" SHARED_LIB="$LIB_DIR/lib.sh" +INSTALLER_SOURCE_STATE="$LIB_DIR/source.env" +LOCAL_LIB_PATH="" -# Source shared helpers (local repo or installed lib) +if [ -f "$INSTALLER_SOURCE_STATE" ]; then + # shellcheck disable=SC1090 + source "$INSTALLER_SOURCE_STATE" + if [ -n "${INSTALLER_SOURCE_ROOT:-}" ] && [ -f "$INSTALLER_SOURCE_ROOT/lib.sh" ]; then + LOCAL_LIB_PATH="$INSTALLER_SOURCE_ROOT/lib.sh" + fi +fi + +# Source shared helpers (local repo, persisted local source, or installed lib) if [ -f "$LIB_PATH" ]; then # shellcheck disable=SC1090 source "$LIB_PATH" +elif [ -n "$LOCAL_LIB_PATH" ]; then + # shellcheck disable=SC1090 + source "$LOCAL_LIB_PATH" elif [ -f "$SHARED_LIB" ]; then # shellcheck disable=SC1090 source "$SHARED_LIB" diff --git a/install.sh b/install.sh index 3513cd1..a7b50d9 100755 --- a/install.sh +++ b/install.sh @@ -94,6 +94,18 @@ chmod +x "$CLI_PATH" log_ok "Cursor installer script has been placed in $CLI_PATH" +INSTALLER_SOURCE_ROOT="" +if [ -f "$LOCAL_CURSOR_SH" ] && [ -f "$LIB_PATH" ]; then + INSTALLER_SOURCE_ROOT="$SCRIPT_DIR" + if command -v git >/dev/null 2>&1; then + DETECTED_REPO_BRANCH=$(git -C "$SCRIPT_DIR" branch --show-current 2>/dev/null || true) + if [ -n "$DETECTED_REPO_BRANCH" ]; then + REPO_BRANCH="$DETECTED_REPO_BRANCH" + fi + fi +fi +persist_installer_source_state "$INSTALLER_SOURCE_ROOT" + log_step "Ensuring cursor shim and shell PATH setup..." LOCAL_SHIM_PATH="$SCRIPT_DIR/shim.sh" LOCAL_SHIM_HELPER_PATH="$SCRIPT_DIR/scripts/ensure-shim.sh" diff --git a/lib.sh b/lib.sh index 55abee4..526660d 100644 --- a/lib.sh +++ b/lib.sh @@ -105,11 +105,68 @@ function find_cursor_appimage() { return 1 } +# --- Installer source metadata --- +INSTALLER_SOURCE_STATE="${LIB_DIR}/source.env" + +function load_installer_source_state() { + if [ ! -f "$INSTALLER_SOURCE_STATE" ]; then + return 0 + fi + + # shellcheck disable=SC1090 + source "$INSTALLER_SOURCE_STATE" +} + +function persist_installer_source_state() { + mkdir -p "$LIB_DIR" + + local source_root="${1:-${INSTALLER_SOURCE_ROOT:-}}" + local tmp_file + tmp_file=$(mktemp) + + { + printf 'INSTALLER_REPO_OWNER=%q\n' "${REPO_OWNER:-watzon}" + printf 'INSTALLER_REPO_BRANCH=%q\n' "${REPO_BRANCH:-main}" + printf 'INSTALLER_REPO_NAME=%q\n' "${REPO_NAME:-cursor-linux-installer}" + + if [ -n "$source_root" ] && [ -d "$source_root" ]; then + printf 'INSTALLER_SOURCE_ROOT=%q\n' "$source_root" + fi + } > "$tmp_file" + + mv "$tmp_file" "$INSTALLER_SOURCE_STATE" +} + +function apply_local_installer_source_overrides() { + local source_root="${INSTALLER_SOURCE_ROOT:-}" + + if [ -z "$source_root" ] || [ ! -d "$source_root" ]; then + return 0 + fi + + if [ -z "${LOCAL_CURSOR_SH:-}" ] && [ -f "$source_root/cursor.sh" ]; then + LOCAL_CURSOR_SH="$source_root/cursor.sh" + fi + if [ -z "${LOCAL_SHIM_PATH:-}" ] && [ -f "$source_root/shim.sh" ]; then + LOCAL_SHIM_PATH="$source_root/shim.sh" + fi + if [ -z "${LOCAL_SHIM_HELPER_PATH:-}" ] && [ -f "$source_root/scripts/ensure-shim.sh" ]; then + LOCAL_SHIM_HELPER_PATH="$source_root/scripts/ensure-shim.sh" + fi + if [ -z "${LOCAL_SHELL_PATH_SCRIPT:-}" ] && [ -f "$source_root/shell-path.sh" ]; then + LOCAL_SHELL_PATH_SCRIPT="$source_root/shell-path.sh" + fi + if [ -z "${LOCAL_SHELL_PATH_HELPER_PATH:-}" ] && [ -f "$source_root/scripts/ensure-shell-path.sh" ]; then + LOCAL_SHELL_PATH_HELPER_PATH="$source_root/scripts/ensure-shell-path.sh" + fi +} + # --- Shim (cursor in PATH): canonical paths and helpers --- # Requires LIB_DIR to be set by caller before sourcing lib. -REPO_OWNER="${REPO_OWNER:-watzon}" -REPO_BRANCH="${REPO_BRANCH:-main}" -REPO_NAME="${REPO_NAME:-cursor-linux-installer}" +load_installer_source_state +REPO_OWNER="${REPO_OWNER:-${INSTALLER_REPO_OWNER:-watzon}}" +REPO_BRANCH="${REPO_BRANCH:-${INSTALLER_REPO_BRANCH:-main}}" +REPO_NAME="${REPO_NAME:-${INSTALLER_REPO_NAME:-cursor-linux-installer}}" BASE_RAW_URL="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${REPO_BRANCH}" SHIM_TARGET="${SHIM_TARGET:-$HOME/.local/bin/cursor}" SHARED_SHIM="${LIB_DIR}/shim.sh" @@ -122,6 +179,7 @@ SHELL_PATH_SCRIPT_URL="${BASE_RAW_URL}/shell-path.sh" SHELL_PATH_HELPER_URL="${BASE_RAW_URL}/scripts/ensure-shell-path.sh" LIB_URL="${BASE_RAW_URL}/lib.sh" CURSOR_SCRIPT_URL="${BASE_RAW_URL}/cursor.sh" +apply_local_installer_source_overrides # Sync shim.sh and ensure-shim.sh into LIB_DIR (local copy or download). # Set LOCAL_SHIM_PATH and/or LOCAL_SHIM_HELPER_PATH to prefer repo files. @@ -161,11 +219,15 @@ function sync_shell_path_assets() { function refresh_shim_assets() { log_step "Refreshing cursor shim assets..." mkdir -p "$LIB_DIR" - if ! curl -fsSL "$SHIM_URL" -o "$SHARED_SHIM"; then + if [ -n "${LOCAL_SHIM_PATH:-}" ] && [ -f "$LOCAL_SHIM_PATH" ]; then + cp "$LOCAL_SHIM_PATH" "$SHARED_SHIM" + elif ! curl -fsSL "$SHIM_URL" -o "$SHARED_SHIM"; then log_warn "Failed to download shim.sh; continuing." return 0 fi - if ! curl -fsSL "$SHIM_HELPER_URL" -o "$SHIM_HELPER"; then + if [ -n "${LOCAL_SHIM_HELPER_PATH:-}" ] && [ -f "$LOCAL_SHIM_HELPER_PATH" ]; then + cp "$LOCAL_SHIM_HELPER_PATH" "$SHIM_HELPER" + elif ! curl -fsSL "$SHIM_HELPER_URL" -o "$SHIM_HELPER"; then log_warn "Failed to download ensure-shim.sh; continuing." return 0 fi @@ -175,11 +237,15 @@ function refresh_shim_assets() { function refresh_shell_path_assets() { log_step "Refreshing shell PATH assets..." mkdir -p "$LIB_DIR" - if ! curl -fsSL "$SHELL_PATH_SCRIPT_URL" -o "$SHELL_PATH_SCRIPT"; then + if [ -n "${LOCAL_SHELL_PATH_SCRIPT:-}" ] && [ -f "$LOCAL_SHELL_PATH_SCRIPT" ]; then + cp "$LOCAL_SHELL_PATH_SCRIPT" "$SHELL_PATH_SCRIPT" + elif ! curl -fsSL "$SHELL_PATH_SCRIPT_URL" -o "$SHELL_PATH_SCRIPT"; then log_warn "Failed to download shell-path.sh; continuing." return 0 fi - if ! curl -fsSL "$SHELL_PATH_HELPER_URL" -o "$SHELL_PATH_HELPER"; then + if [ -n "${LOCAL_SHELL_PATH_HELPER_PATH:-}" ] && [ -f "$LOCAL_SHELL_PATH_HELPER_PATH" ]; then + cp "$LOCAL_SHELL_PATH_HELPER_PATH" "$SHELL_PATH_HELPER" + elif ! curl -fsSL "$SHELL_PATH_HELPER_URL" -o "$SHELL_PATH_HELPER"; then log_warn "Failed to download ensure-shell-path.sh; continuing." return 0 fi diff --git a/uninstall.sh b/uninstall.sh index 9213a73..b1d405f 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -57,6 +57,7 @@ safe_remove "$SHARED_SHIM" "cursor shim source" safe_remove "$SHIM_HELPER" "cursor shim helper" safe_remove "$SHELL_PATH_SCRIPT" "shell PATH helper script" safe_remove "$SHELL_PATH_HELPER" "shell PATH helper" +safe_remove "$INSTALLER_SOURCE_STATE" "installer source metadata" safe_remove "$SHARED_LIB" "cursor-installer lib" if [ -d "$LIB_DIR" ] && [ -z "$(ls -A "$LIB_DIR")" ]; then rmdir "$LIB_DIR" 2>/dev/null || true From b0e64a7a13689509b0035538f1251a6d36260ea4 Mon Sep 17 00:00:00 2001 From: ZanzyTHEbar Date: Tue, 31 Mar 2026 17:18:41 +0100 Subject: [PATCH 6/6] fix: harden mixed-version updater bootstrap Prefer current installer assets when available, keep shell-path helpers compatible across mixed versions, and avoid corrupting cached refresh assets on failed downloads. --- cursor.sh | 69 +++++++++++++++++++++++++++++++++++------ install.sh | 7 ++--- lib.sh | 86 +++++++++++++++++++++++++++++++++++++++++++--------- uninstall.sh | 29 ++++++++++++++++-- 4 files changed, 161 insertions(+), 30 deletions(-) diff --git a/cursor.sh b/cursor.sh index 5b483e8..7b5e6dd 100755 --- a/cursor.sh +++ b/cursor.sh @@ -8,6 +8,9 @@ LIB_PATH="$SCRIPT_DIR/lib.sh" SHARED_LIB="$LIB_DIR/lib.sh" INSTALLER_SOURCE_STATE="$LIB_DIR/source.env" LOCAL_LIB_PATH="" +BOOTSTRAP_REPO_OWNER="${REPO_OWNER:-watzon}" +BOOTSTRAP_REPO_BRANCH="${REPO_BRANCH:-main}" +BOOTSTRAP_REPO_NAME="${REPO_NAME:-cursor-linux-installer}" if [ -f "$INSTALLER_SOURCE_STATE" ]; then # shellcheck disable=SC1090 @@ -15,8 +18,13 @@ if [ -f "$INSTALLER_SOURCE_STATE" ]; then if [ -n "${INSTALLER_SOURCE_ROOT:-}" ] && [ -f "$INSTALLER_SOURCE_ROOT/lib.sh" ]; then LOCAL_LIB_PATH="$INSTALLER_SOURCE_ROOT/lib.sh" fi + BOOTSTRAP_REPO_OWNER="${INSTALLER_REPO_OWNER:-$BOOTSTRAP_REPO_OWNER}" + BOOTSTRAP_REPO_BRANCH="${INSTALLER_REPO_BRANCH:-$BOOTSTRAP_REPO_BRANCH}" + BOOTSTRAP_REPO_NAME="${INSTALLER_REPO_NAME:-$BOOTSTRAP_REPO_NAME}" fi +BOOTSTRAP_LIB_URL="https://raw.githubusercontent.com/${BOOTSTRAP_REPO_OWNER}/${BOOTSTRAP_REPO_NAME}/${BOOTSTRAP_REPO_BRANCH}/lib.sh" + # Source shared helpers (local repo, persisted local source, or installed lib) if [ -f "$LIB_PATH" ]; then # shellcheck disable=SC1090 @@ -32,6 +40,49 @@ else exit 1 fi +function ensure_shell_path_lib_apis() { + if declare -F refresh_shell_path_assets >/dev/null 2>&1 && + declare -F run_ensure_shell_path >/dev/null 2>&1 && + declare -F run_remove_shell_path >/dev/null 2>&1 && + declare -F warn_if_cursor_shadowed_by_appimage_runtime >/dev/null 2>&1; then + return 0 + fi + + if [ -n "$LOCAL_LIB_PATH" ] && [ -f "$LOCAL_LIB_PATH" ]; then + # shellcheck disable=SC1090 + source "$LOCAL_LIB_PATH" + else + mkdir -p "$LIB_DIR" + if curl -fsSL "$BOOTSTRAP_LIB_URL" -o "$SHARED_LIB"; then + # shellcheck disable=SC1090 + source "$SHARED_LIB" + else + log_info "Latest shell PATH helper API unavailable; continuing in compatibility mode." + return 1 + fi + fi + + if declare -F refresh_shell_path_assets >/dev/null 2>&1 && + declare -F run_ensure_shell_path >/dev/null 2>&1 && + declare -F run_remove_shell_path >/dev/null 2>&1 && + declare -F warn_if_cursor_shadowed_by_appimage_runtime >/dev/null 2>&1; then + return 0 + fi + + log_info "Latest shell PATH helper API unavailable; continuing in compatibility mode." + return 1 +} + +function run_optional_helper() { + local helper="$1" + + if declare -F "$helper" >/dev/null 2>&1; then + "$helper" + fi +} + +ensure_shell_path_lib_apis || true + CLI_NAME="cursor-installer" CLI_BIN="$HOME/.local/bin/$CLI_NAME" @@ -302,8 +353,8 @@ EOF } function install_cursor_extracted() { - run_ensure_shim - run_ensure_shell_path + run_optional_helper run_ensure_shim + run_optional_helper run_ensure_shell_path local install_dir="$1" local release_track=${2:-stable} local temp_file @@ -453,8 +504,8 @@ function install_cursor_extracted() { } function install_cursor() { - run_ensure_shim - run_ensure_shell_path + run_optional_helper run_ensure_shim + run_optional_helper run_ensure_shell_path local install_dir="$1" local release_track=${2:-stable} # Default to stable if not specified @@ -680,11 +731,11 @@ EOF function update_cursor() { log_step "Updating Cursor..." - refresh_shim_assets - refresh_shell_path_assets - run_ensure_shim - run_ensure_shell_path - warn_if_cursor_shadowed_by_appimage_runtime + run_optional_helper refresh_shim_assets + run_optional_helper refresh_shell_path_assets + run_optional_helper run_ensure_shim + run_optional_helper run_ensure_shell_path + run_optional_helper warn_if_cursor_shadowed_by_appimage_runtime local current_appimage current_appimage=$(find_cursor_appimage || true) local install_dir diff --git a/install.sh b/install.sh index a7b50d9..e31f2b6 100755 --- a/install.sh +++ b/install.sh @@ -42,15 +42,14 @@ BASE_RAW_URL="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${REP LIB_URL="${BASE_RAW_URL}/lib.sh" CURSOR_SCRIPT_URL="${BASE_RAW_URL}/cursor.sh" -# Source shared helpers (local repo, installed lib, or download) +# Source shared helpers (local repo or freshly downloaded lib). +# Standalone installs must not rely on a potentially stale shared lib, because +# this script calls newer helper APIs later in the bootstrap flow. if [ -f "$LIB_PATH" ]; then # shellcheck disable=SC1090 source "$LIB_PATH" mkdir -p "$LIB_DIR" cp "$LIB_PATH" "$SHARED_LIB" -elif [ -f "$SHARED_LIB" ]; then - # shellcheck disable=SC1090 - source "$SHARED_LIB" else mkdir -p "$LIB_DIR" curl -fsSL "$LIB_URL" -o "$SHARED_LIB" || { diff --git a/lib.sh b/lib.sh index 526660d..454dacb 100644 --- a/lib.sh +++ b/lib.sh @@ -161,6 +161,57 @@ function apply_local_installer_source_overrides() { fi } +function get_managed_shell_files() { + local candidate + local files=() + + for candidate in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do + if [ -e "$candidate" ] || [ -L "$candidate" ]; then + files+=("$candidate") + fi + done + + if [ ${#files[@]} -eq 0 ]; then + case "$(basename "${SHELL:-}")" in + bash) + files+=("$HOME/.bashrc") + ;; + zsh) + files+=("$HOME/.zshrc") + ;; + sh|dash|ksh) + files+=("$HOME/.profile") + ;; + esac + fi + + if [ ${#files[@]} -eq 0 ]; then + return 1 + fi + + local old_ifs="$IFS" + IFS=: + printf '%s' "${files[*]}" + IFS="$old_ifs" +} + +function download_asset_atomically() { + local url="$1" + local destination="$2" + local description="$3" + local tmp_file + + tmp_file=$(mktemp) + if curl -fsSL "$url" -o "$tmp_file"; then + mv "$tmp_file" "$destination" + return 0 + fi + + rm -f "$tmp_file" + log_warn "Failed to download $description" + return 1 +} + # --- Shim (cursor in PATH): canonical paths and helpers --- # Requires LIB_DIR to be set by caller before sourcing lib. load_installer_source_state @@ -188,12 +239,12 @@ function sync_shim_assets() { if [ -n "${LOCAL_SHIM_PATH:-}" ] && [ -f "$LOCAL_SHIM_PATH" ]; then cp "$LOCAL_SHIM_PATH" "$SHARED_SHIM" elif [ ! -f "$SHARED_SHIM" ]; then - curl -fsSL "$SHIM_URL" -o "$SHARED_SHIM" || { log_warn "Failed to download shim.sh"; return 1; } + download_asset_atomically "$SHIM_URL" "$SHARED_SHIM" "shim.sh" || return 1 fi if [ -n "${LOCAL_SHIM_HELPER_PATH:-}" ] && [ -f "$LOCAL_SHIM_HELPER_PATH" ]; then cp "$LOCAL_SHIM_HELPER_PATH" "$SHIM_HELPER" elif [ ! -f "$SHIM_HELPER" ]; then - curl -fsSL "$SHIM_HELPER_URL" -o "$SHIM_HELPER" || { log_warn "Failed to download ensure-shim.sh"; return 1; } + download_asset_atomically "$SHIM_HELPER_URL" "$SHIM_HELPER" "ensure-shim.sh" || return 1 fi chmod +x "$SHIM_HELPER" "$SHARED_SHIM" 2>/dev/null || true return 0 @@ -204,12 +255,12 @@ function sync_shell_path_assets() { if [ -n "${LOCAL_SHELL_PATH_SCRIPT:-}" ] && [ -f "$LOCAL_SHELL_PATH_SCRIPT" ]; then cp "$LOCAL_SHELL_PATH_SCRIPT" "$SHELL_PATH_SCRIPT" elif [ ! -f "$SHELL_PATH_SCRIPT" ]; then - curl -fsSL "$SHELL_PATH_SCRIPT_URL" -o "$SHELL_PATH_SCRIPT" || { log_warn "Failed to download shell-path.sh"; return 1; } + download_asset_atomically "$SHELL_PATH_SCRIPT_URL" "$SHELL_PATH_SCRIPT" "shell-path.sh" || return 1 fi if [ -n "${LOCAL_SHELL_PATH_HELPER_PATH:-}" ] && [ -f "$LOCAL_SHELL_PATH_HELPER_PATH" ]; then cp "$LOCAL_SHELL_PATH_HELPER_PATH" "$SHELL_PATH_HELPER" elif [ ! -f "$SHELL_PATH_HELPER" ]; then - curl -fsSL "$SHELL_PATH_HELPER_URL" -o "$SHELL_PATH_HELPER" || { log_warn "Failed to download ensure-shell-path.sh"; return 1; } + download_asset_atomically "$SHELL_PATH_HELPER_URL" "$SHELL_PATH_HELPER" "ensure-shell-path.sh" || return 1 fi chmod +x "$SHELL_PATH_HELPER" "$SHELL_PATH_SCRIPT" 2>/dev/null || true return 0 @@ -221,14 +272,12 @@ function refresh_shim_assets() { mkdir -p "$LIB_DIR" if [ -n "${LOCAL_SHIM_PATH:-}" ] && [ -f "$LOCAL_SHIM_PATH" ]; then cp "$LOCAL_SHIM_PATH" "$SHARED_SHIM" - elif ! curl -fsSL "$SHIM_URL" -o "$SHARED_SHIM"; then - log_warn "Failed to download shim.sh; continuing." + elif ! download_asset_atomically "$SHIM_URL" "$SHARED_SHIM" "shim.sh"; then return 0 fi if [ -n "${LOCAL_SHIM_HELPER_PATH:-}" ] && [ -f "$LOCAL_SHIM_HELPER_PATH" ]; then cp "$LOCAL_SHIM_HELPER_PATH" "$SHIM_HELPER" - elif ! curl -fsSL "$SHIM_HELPER_URL" -o "$SHIM_HELPER"; then - log_warn "Failed to download ensure-shim.sh; continuing." + elif ! download_asset_atomically "$SHIM_HELPER_URL" "$SHIM_HELPER" "ensure-shim.sh"; then return 0 fi chmod +x "$SHIM_HELPER" "$SHARED_SHIM" 2>/dev/null || true @@ -239,14 +288,12 @@ function refresh_shell_path_assets() { mkdir -p "$LIB_DIR" if [ -n "${LOCAL_SHELL_PATH_SCRIPT:-}" ] && [ -f "$LOCAL_SHELL_PATH_SCRIPT" ]; then cp "$LOCAL_SHELL_PATH_SCRIPT" "$SHELL_PATH_SCRIPT" - elif ! curl -fsSL "$SHELL_PATH_SCRIPT_URL" -o "$SHELL_PATH_SCRIPT"; then - log_warn "Failed to download shell-path.sh; continuing." + elif ! download_asset_atomically "$SHELL_PATH_SCRIPT_URL" "$SHELL_PATH_SCRIPT" "shell-path.sh"; then return 0 fi if [ -n "${LOCAL_SHELL_PATH_HELPER_PATH:-}" ] && [ -f "$LOCAL_SHELL_PATH_HELPER_PATH" ]; then cp "$LOCAL_SHELL_PATH_HELPER_PATH" "$SHELL_PATH_HELPER" - elif ! curl -fsSL "$SHELL_PATH_HELPER_URL" -o "$SHELL_PATH_HELPER"; then - log_warn "Failed to download ensure-shell-path.sh; continuing." + elif ! download_asset_atomically "$SHELL_PATH_HELPER_URL" "$SHELL_PATH_HELPER" "ensure-shell-path.sh"; then return 0 fi chmod +x "$SHELL_PATH_HELPER" "$SHELL_PATH_SCRIPT" 2>/dev/null || true @@ -266,14 +313,25 @@ function run_ensure_shell_path() { log_info "Shell PATH helper not found; skipping shell PATH setup." return 0 fi - SHELL_PATH_SCRIPT="$SHELL_PATH_SCRIPT" sh "$SHELL_PATH_HELPER" || { log_warn "Shell PATH setup failed; continuing."; return 0; } + local target_shell_files + target_shell_files="${TARGET_SHELL_FILES:-${MANAGED_SHELL_FILES:-$(get_managed_shell_files || true)}}" + if [ -z "$target_shell_files" ]; then + log_info "No managed shell rc files detected; skipping shell PATH setup." + return 0 + fi + TARGET_SHELL_FILES="$target_shell_files" SHELL_PATH_SCRIPT="$SHELL_PATH_SCRIPT" sh "$SHELL_PATH_HELPER" || { log_warn "Shell PATH setup failed; continuing."; return 0; } } function run_remove_shell_path() { if [ ! -f "$SHELL_PATH_HELPER" ]; then return 0 fi - SHELL_PATH_SCRIPT="$SHELL_PATH_SCRIPT" sh "$SHELL_PATH_HELPER" --remove || { log_warn "Shell PATH cleanup failed; continuing."; return 0; } + local target_shell_files + target_shell_files="${TARGET_SHELL_FILES:-${MANAGED_SHELL_FILES:-$(get_managed_shell_files || true)}}" + if [ -z "$target_shell_files" ]; then + return 0 + fi + TARGET_SHELL_FILES="$target_shell_files" SHELL_PATH_SCRIPT="$SHELL_PATH_SCRIPT" sh "$SHELL_PATH_HELPER" --remove || { log_warn "Shell PATH cleanup failed; continuing."; return 0; } } function warn_if_cursor_shadowed_by_appimage_runtime() { diff --git a/uninstall.sh b/uninstall.sh index b1d405f..3153651 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -11,11 +11,24 @@ LIB_DIR="$HOME/.local/share/cursor-installer" LIB_PATH="$SCRIPT_DIR/lib.sh" SHARED_LIB="$LIB_DIR/lib.sh" LIB_URL="$BASE_RAW_URL/lib.sh" +INSTALLER_SOURCE_STATE="$LIB_DIR/source.env" +LOCAL_LIB_PATH="" -# Source shared helpers (local repo, installed lib, or download) +if [ -f "$INSTALLER_SOURCE_STATE" ]; then + # shellcheck disable=SC1090 + source "$INSTALLER_SOURCE_STATE" + if [ -n "${INSTALLER_SOURCE_ROOT:-}" ] && [ -f "$INSTALLER_SOURCE_ROOT/lib.sh" ]; then + LOCAL_LIB_PATH="$INSTALLER_SOURCE_ROOT/lib.sh" + fi +fi + +# Source shared helpers (local repo, persisted local source, installed lib, or download) if [ -f "$LIB_PATH" ]; then # shellcheck disable=SC1090 source "$LIB_PATH" +elif [ -n "$LOCAL_LIB_PATH" ]; then + # shellcheck disable=SC1090 + source "$LOCAL_LIB_PATH" elif [ -f "$SHARED_LIB" ]; then # shellcheck disable=SC1090 source "$SHARED_LIB" @@ -29,6 +42,12 @@ else source "$SHARED_LIB" fi +SHARED_SHIM="${SHARED_SHIM:-$LIB_DIR/shim.sh}" +SHIM_HELPER="${SHIM_HELPER:-$LIB_DIR/ensure-shim.sh}" +SHELL_PATH_SCRIPT="${SHELL_PATH_SCRIPT:-$LIB_DIR/shell-path.sh}" +SHELL_PATH_HELPER="${SHELL_PATH_HELPER:-$LIB_DIR/ensure-shell-path.sh}" +INSTALLER_SOURCE_STATE="${INSTALLER_SOURCE_STATE:-$LIB_DIR/source.env}" + CLI_NAME="cursor-installer" CLI_PATH="$HOME/.local/bin/$CLI_NAME" LEGACY_CLI="$HOME/.local/bin/cursor" @@ -36,7 +55,7 @@ LEGACY_CLI="$HOME/.local/bin/cursor" log_step "Uninstalling Cursor..." # Remove the Cursor AppImage -cursor_appimage=$(find_cursor_appimage) +cursor_appimage=$(find_cursor_appimage || true) if [ -n "$cursor_appimage" ]; then log_step "Removing Cursor AppImage..." safe_remove "$cursor_appimage" "Cursor AppImage" @@ -50,7 +69,11 @@ safe_remove "$CLI_PATH" "cursor-installer script" # Remove managed shell PATH setup before deleting helper assets log_step "Removing managed shell PATH setup..." -run_remove_shell_path +if declare -F run_remove_shell_path >/dev/null 2>&1; then + run_remove_shell_path +else + log_info "Shell PATH cleanup helper unavailable; skipping managed shell PATH setup removal." +fi # Remove shared support assets (installed by installer) safe_remove "$SHARED_SHIM" "cursor shim source"