Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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"
Expand Down
78 changes: 74 additions & 4 deletions cursor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,32 @@ 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=""
BOOTSTRAP_REPO_OWNER="${REPO_OWNER:-watzon}"
BOOTSTRAP_REPO_BRANCH="${REPO_BRANCH:-main}"
BOOTSTRAP_REPO_NAME="${REPO_NAME:-cursor-linux-installer}"

# 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
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
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"
Expand All @@ -19,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"

Expand Down Expand Up @@ -289,7 +353,8 @@ EOF
}

function install_cursor_extracted() {
run_ensure_shim
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
Expand Down Expand Up @@ -439,7 +504,8 @@ function install_cursor_extracted() {
}

function install_cursor() {
run_ensure_shim
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

Expand Down Expand Up @@ -665,7 +731,11 @@ EOF

function update_cursor() {
log_step "Updating Cursor..."
refresh_shim_assets
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
Expand Down
28 changes: 22 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" || {
Expand Down Expand Up @@ -94,15 +93,32 @@ 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."
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"
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
log_warn "$LOCAL_BIN is not in your PATH."
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}..."
Expand Down
Loading
Loading