Skip to content

Commit 4e669bc

Browse files
committed
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.
1 parent f4b5c3f commit 4e669bc

9 files changed

Lines changed: 355 additions & 26 deletions

File tree

README.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ The one‑liner script will:
4444
2. Download `lib.sh` to `~/.local/share/cursor-installer/lib.sh`
4545
3. Make the script executable
4646
4. Install a `cursor` shim at `~/.local/bin/cursor` (see [The `cursor` Shim](#the-cursor-shim))
47-
5. Download and install the latest version of Cursor
47+
5. Install a managed shell startup hook for supported shells so `~/.local/bin` stays ahead of transient AppImage runtime paths
48+
6. Download and install the latest version of Cursor
4849

4950
**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:
5051

@@ -125,7 +126,8 @@ The uninstall script will:
125126
1. Remove the `cursor-installer` script from `~/.local/bin/`
126127
2. Remove the shared `lib.sh` from `~/.local/share/cursor-installer/`
127128
3. Remove the Cursor AppImage
128-
4. Ask if you want to remove the Cursor configuration files
129+
4. Remove the managed shell PATH hook from supported shell startup files
130+
5. Ask if you want to remove the Cursor configuration files
129131

130132
**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.
131133

@@ -211,12 +213,19 @@ The shim bridges that gap. It installs a lightweight script at `~/.local/bin/cur
211213

212214
When you type `cursor`, the shim (`~/.local/bin/cursor`) follows a short resolution chain:
213215

214-
1. **Real Cursor binary found in PATH?** -- Forward all arguments to it (e.g. Cursor's official `cursor` CLI).
215-
2. **`cursor agent` subcommand?** -- Delegate to `~/.local/bin/agent` if it exists.
216-
3. **`cursor-installer` found?** -- Delegate to the installer CLI so commands like `cursor --update` still work.
217-
4. **Nothing found** -- Print a helpful error with install instructions.
216+
1. **`cursor agent` subcommand?** -- Delegate to `~/.local/bin/agent` if it exists.
217+
2. **Installer-only flag?** -- Delegate to `cursor-installer` for commands like `cursor --update`, `cursor --check`, or `cursor --extract`.
218+
3. **Stable Cursor binary found in PATH?** -- Forward all other arguments to it (e.g. Cursor's official `cursor` CLI).
219+
4. **`cursor-installer` found?** -- Delegate to the installer CLI as a general fallback.
220+
5. **Nothing found** -- Print a helpful error with install instructions.
218221

219-
The shim never hides a real Cursor binary; it only acts as a fallback.
222+
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.
223+
224+
### AppImage Terminals
225+
226+
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.
227+
228+
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.
220229

221230
### How It Works
222231

@@ -241,6 +250,7 @@ The shim is synced automatically during normal installer operations:
241250
- **`install.sh`** -- Copies `shim.sh` and `ensure-shim.sh` into `~/.local/share/cursor-installer/`, then runs `ensure-shim.sh`.
242251
- **`cursor-installer --update`** -- Re-downloads the latest shim assets from GitHub, then re-runs `ensure-shim.sh`.
243252
- **`cursor-installer` (install paths)** -- Runs `ensure-shim.sh` before each install to keep the shim current.
253+
- **Shell PATH setup** -- Syncs `shell-path.sh` and `ensure-shell-path.sh`, then ensures supported shell startup files source the PATH helper.
244254

245255
### File Locations
246256

@@ -249,6 +259,8 @@ The shim is synced automatically during normal installer operations:
249259
| `~/.local/bin/cursor` | The shim (what you invoke). |
250260
| `~/.local/share/cursor-installer/shim.sh` | Cached copy of the shim source. |
251261
| `~/.local/share/cursor-installer/ensure-shim.sh` | Cached copy of the installer helper. |
262+
| `~/.local/share/cursor-installer/shell-path.sh` | Shell snippet that prepends `~/.local/bin`. |
263+
| `~/.local/share/cursor-installer/ensure-shell-path.sh` | Helper that updates supported shell startup files. |
252264

253265
### Removing the Shim
254266

@@ -264,7 +276,7 @@ If you only want to disable the shim without uninstalling the rest of the projec
264276

265277
## Note
266278

267-
If you encounter a warning that `~/.local/bin` is not in your PATH, you can add it by running:
279+
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:
268280

269281
```bash
270282
export PATH="$HOME/.local/bin:$PATH"

cursor.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ EOF
290290

291291
function install_cursor_extracted() {
292292
run_ensure_shim
293+
run_ensure_shell_path
293294
local install_dir="$1"
294295
local release_track=${2:-stable}
295296
local temp_file
@@ -440,6 +441,7 @@ function install_cursor_extracted() {
440441

441442
function install_cursor() {
442443
run_ensure_shim
444+
run_ensure_shell_path
443445
local install_dir="$1"
444446
local release_track=${2:-stable} # Default to stable if not specified
445447

@@ -666,6 +668,10 @@ EOF
666668
function update_cursor() {
667669
log_step "Updating Cursor..."
668670
refresh_shim_assets
671+
refresh_shell_path_assets
672+
run_ensure_shim
673+
run_ensure_shell_path
674+
warn_if_cursor_shadowed_by_appimage_runtime
669675
local current_appimage
670676
current_appimage=$(find_cursor_appimage || true)
671677
local install_dir

install.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,20 @@ chmod +x "$CLI_PATH"
9494

9595
log_ok "Cursor installer script has been placed in $CLI_PATH"
9696

97-
log_step "Ensuring cursor shim..."
98-
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."
97+
log_step "Ensuring cursor shim and shell PATH setup..."
98+
LOCAL_SHIM_PATH="$SCRIPT_DIR/shim.sh"
99+
LOCAL_SHIM_HELPER_PATH="$SCRIPT_DIR/scripts/ensure-shim.sh"
100+
LOCAL_SHELL_PATH_SCRIPT="$SCRIPT_DIR/shell-path.sh"
101+
LOCAL_SHELL_PATH_HELPER_PATH="$SCRIPT_DIR/scripts/ensure-shell-path.sh"
102+
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."
99103

100104
# Check if ~/.local/bin is in PATH
101105
if [[ ":$PATH:" != *":$LOCAL_BIN:"* ]]; then
102106
log_warn "$LOCAL_BIN is not in your PATH."
103107
log_info "To add it, run this or add it to your shell profile:"
104108
log_info "export PATH=\"\$HOME/.local/bin:\$PATH\""
105109
fi
110+
warn_if_cursor_shadowed_by_appimage_runtime
106111

107112
# Run cursor --update to download and install Cursor
108113
log_step "Downloading and installing Cursor ($INSTALL_MODE mode) from ${REPO_OWNER}/${REPO_NAME}@${REPO_BRANCH}..."

lib.sh

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,12 @@ BASE_RAW_URL="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${REP
114114
SHIM_TARGET="${SHIM_TARGET:-$HOME/.local/bin/cursor}"
115115
SHARED_SHIM="${LIB_DIR}/shim.sh"
116116
SHIM_HELPER="${LIB_DIR}/ensure-shim.sh"
117+
SHELL_PATH_SCRIPT="${LIB_DIR}/shell-path.sh"
118+
SHELL_PATH_HELPER="${LIB_DIR}/ensure-shell-path.sh"
117119
SHIM_URL="${BASE_RAW_URL}/shim.sh"
118120
SHIM_HELPER_URL="${BASE_RAW_URL}/scripts/ensure-shim.sh"
121+
SHELL_PATH_SCRIPT_URL="${BASE_RAW_URL}/shell-path.sh"
122+
SHELL_PATH_HELPER_URL="${BASE_RAW_URL}/scripts/ensure-shell-path.sh"
119123
LIB_URL="${BASE_RAW_URL}/lib.sh"
120124
CURSOR_SCRIPT_URL="${BASE_RAW_URL}/cursor.sh"
121125

@@ -137,6 +141,22 @@ function sync_shim_assets() {
137141
return 0
138142
}
139143

144+
function sync_shell_path_assets() {
145+
mkdir -p "$LIB_DIR"
146+
if [ -n "${LOCAL_SHELL_PATH_SCRIPT:-}" ] && [ -f "$LOCAL_SHELL_PATH_SCRIPT" ]; then
147+
cp "$LOCAL_SHELL_PATH_SCRIPT" "$SHELL_PATH_SCRIPT"
148+
elif [ ! -f "$SHELL_PATH_SCRIPT" ]; then
149+
curl -fsSL "$SHELL_PATH_SCRIPT_URL" -o "$SHELL_PATH_SCRIPT" || { log_warn "Failed to download shell-path.sh"; return 1; }
150+
fi
151+
if [ -n "${LOCAL_SHELL_PATH_HELPER_PATH:-}" ] && [ -f "$LOCAL_SHELL_PATH_HELPER_PATH" ]; then
152+
cp "$LOCAL_SHELL_PATH_HELPER_PATH" "$SHELL_PATH_HELPER"
153+
elif [ ! -f "$SHELL_PATH_HELPER" ]; then
154+
curl -fsSL "$SHELL_PATH_HELPER_URL" -o "$SHELL_PATH_HELPER" || { log_warn "Failed to download ensure-shell-path.sh"; return 1; }
155+
fi
156+
chmod +x "$SHELL_PATH_HELPER" "$SHELL_PATH_SCRIPT" 2>/dev/null || true
157+
return 0
158+
}
159+
140160
# Refresh shim assets from GitHub (used on cursor-installer --update).
141161
function refresh_shim_assets() {
142162
log_step "Refreshing cursor shim assets..."
@@ -152,11 +172,52 @@ function refresh_shim_assets() {
152172
chmod +x "$SHIM_HELPER" "$SHARED_SHIM" 2>/dev/null || true
153173
}
154174

175+
function refresh_shell_path_assets() {
176+
log_step "Refreshing shell PATH assets..."
177+
mkdir -p "$LIB_DIR"
178+
if ! curl -fsSL "$SHELL_PATH_SCRIPT_URL" -o "$SHELL_PATH_SCRIPT"; then
179+
log_warn "Failed to download shell-path.sh; continuing."
180+
return 0
181+
fi
182+
if ! curl -fsSL "$SHELL_PATH_HELPER_URL" -o "$SHELL_PATH_HELPER"; then
183+
log_warn "Failed to download ensure-shell-path.sh; continuing."
184+
return 0
185+
fi
186+
chmod +x "$SHELL_PATH_HELPER" "$SHELL_PATH_SCRIPT" 2>/dev/null || true
187+
}
188+
155189
# Run ensure-shim.sh with canonical SOURCE_SHIM and TARGET_SHIM.
156190
function run_ensure_shim() {
157-
if [ ! -x "$SHIM_HELPER" ] && [ ! -f "$SHIM_HELPER" ]; then
191+
if [ ! -f "$SHIM_HELPER" ]; then
158192
log_info "Shim helper not found; skipping shim update."
159193
return 0
160194
fi
161-
SOURCE_SHIM="$SHARED_SHIM" TARGET_SHIM="$SHIM_TARGET" "$SHIM_HELPER" || { log_warn "Shim update failed; continuing."; return 0; }
195+
SOURCE_SHIM="$SHARED_SHIM" TARGET_SHIM="$SHIM_TARGET" sh "$SHIM_HELPER" || { log_warn "Shim update failed; continuing."; return 0; }
196+
}
197+
198+
function run_ensure_shell_path() {
199+
if [ ! -f "$SHELL_PATH_HELPER" ] || [ ! -f "$SHELL_PATH_SCRIPT" ]; then
200+
log_info "Shell PATH helper not found; skipping shell PATH setup."
201+
return 0
202+
fi
203+
SHELL_PATH_SCRIPT="$SHELL_PATH_SCRIPT" sh "$SHELL_PATH_HELPER" || { log_warn "Shell PATH setup failed; continuing."; return 0; }
204+
}
205+
206+
function run_remove_shell_path() {
207+
if [ ! -f "$SHELL_PATH_HELPER" ]; then
208+
return 0
209+
fi
210+
SHELL_PATH_SCRIPT="$SHELL_PATH_SCRIPT" sh "$SHELL_PATH_HELPER" --remove || { log_warn "Shell PATH cleanup failed; continuing."; return 0; }
211+
}
212+
213+
function warn_if_cursor_shadowed_by_appimage_runtime() {
214+
local resolved_cursor
215+
resolved_cursor=$(command -v cursor 2>/dev/null || true)
216+
217+
case "$resolved_cursor" in
218+
/tmp/.mount_*)
219+
log_warn "The current shell resolves 'cursor' to Cursor's AppImage runtime path."
220+
log_info "Open a new terminal or source your shell startup file so ~/.local/bin takes precedence."
221+
;;
222+
esac
162223
}

scripts/ensure-shell-path.sh

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/bin/sh
2+
# Ensure supported interactive shells source cursor-installer's PATH helper.
3+
set -eu
4+
5+
ACTION="${1:-ensure}"
6+
LIB_DIR="${HOME}/.local/share/cursor-installer"
7+
SHELL_PATH_SCRIPT="${SHELL_PATH_SCRIPT:-$LIB_DIR/shell-path.sh}"
8+
START_MARKER="# >>> cursor-installer path >>>"
9+
END_MARKER="# <<< cursor-installer path <<<"
10+
11+
build_source_block() {
12+
cat <<EOF
13+
$START_MARKER
14+
if [ -f "$SHELL_PATH_SCRIPT" ]; then
15+
. "$SHELL_PATH_SCRIPT"
16+
fi
17+
$END_MARKER
18+
EOF
19+
}
20+
21+
print_target_files() {
22+
if [ -n "${TARGET_SHELL_FILES:-}" ]; then
23+
old_IFS="$IFS"
24+
IFS=:
25+
for file in $TARGET_SHELL_FILES; do
26+
[ -n "$file" ] && printf '%s\n' "$file"
27+
done
28+
IFS="$old_IFS"
29+
return 0
30+
fi
31+
32+
if [ -n "${TARGET_SHELL_RC:-}" ]; then
33+
printf '%s\n' "$TARGET_SHELL_RC"
34+
return 0
35+
fi
36+
37+
shell_name=$(basename "${SHELL:-}")
38+
case "$shell_name" in
39+
bash)
40+
printf '%s\n' "$HOME/.bashrc"
41+
;;
42+
zsh)
43+
printf '%s\n' "$HOME/.zshrc"
44+
;;
45+
sh|dash|ksh)
46+
printf '%s\n' "$HOME/.profile"
47+
;;
48+
*)
49+
echo "Skipping shell PATH setup; unsupported shell: ${SHELL:-unknown}" >&2
50+
return 1
51+
;;
52+
esac
53+
}
54+
55+
strip_managed_block() {
56+
file="$1"
57+
tmp="$2"
58+
59+
if [ -f "$file" ]; then
60+
awk -v start="$START_MARKER" -v end="$END_MARKER" '
61+
$0 == start { skip = 1; next }
62+
skip && $0 == end { skip = 0; next }
63+
!skip { print }
64+
' "$file" > "$tmp"
65+
else
66+
: > "$tmp"
67+
fi
68+
}
69+
70+
ensure_block() {
71+
file="$1"
72+
tmp=$(mktemp)
73+
mkdir -p "$(dirname "$file")"
74+
strip_managed_block "$file" "$tmp"
75+
76+
if [ -s "$tmp" ]; then
77+
printf '\n' >> "$tmp"
78+
fi
79+
80+
build_source_block >> "$tmp"
81+
82+
if [ -f "$file" ] && cmp -s "$tmp" "$file"; then
83+
rm -f "$tmp"
84+
echo "Shell PATH setup already present in $file"
85+
return 0
86+
fi
87+
88+
mv "$tmp" "$file"
89+
echo "Ensured shell PATH setup in $file"
90+
}
91+
92+
remove_block() {
93+
file="$1"
94+
[ -f "$file" ] || return 0
95+
96+
tmp=$(mktemp)
97+
strip_managed_block "$file" "$tmp"
98+
99+
if cmp -s "$tmp" "$file"; then
100+
rm -f "$tmp"
101+
return 0
102+
fi
103+
104+
mv "$tmp" "$file"
105+
echo "Removed shell PATH setup from $file"
106+
}
107+
108+
if [ "$ACTION" = "ensure" ] && [ ! -f "$SHELL_PATH_SCRIPT" ]; then
109+
echo "Error: shell-path.sh source not found at $SHELL_PATH_SCRIPT" >&2
110+
exit 1
111+
fi
112+
113+
if ! target_files=$(print_target_files); then
114+
exit 0
115+
fi
116+
117+
printf '%s\n' "$target_files" | while IFS= read -r file; do
118+
[ -n "$file" ] || continue
119+
120+
case "$ACTION" in
121+
ensure)
122+
ensure_block "$file"
123+
;;
124+
--remove|remove)
125+
remove_block "$file"
126+
;;
127+
*)
128+
echo "Unknown action: $ACTION" >&2
129+
exit 1
130+
;;
131+
esac
132+
done

scripts/ensure-shim.sh

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set -eu
55
TARGET_SHIM="${TARGET_SHIM:-$HOME/.local/bin/cursor}"
66
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
77
LIB_DIR="${HOME}/.local/share/cursor-installer"
8+
SHIM_MARKER="cursor-linux-installer-shim"
89

910
SOURCE_SHIM="${SOURCE_SHIM:-}"
1011
if [ -z "$SOURCE_SHIM" ]; then
@@ -36,12 +37,17 @@ is_shim() {
3637
return 1
3738
;;
3839
esac
39-
if grep -q "Find cursor executable in PATH" "$file" 2>/dev/null; then
40+
41+
if grep -Fq "$SHIM_MARKER" "$file" 2>/dev/null; then
4042
return 0
4143
fi
42-
if grep -q "cursor-installer" "$file" 2>/dev/null; then
44+
45+
if grep -Fq "Find cursor executable in PATH" "$file" 2>/dev/null &&
46+
grep -Fq 'AGENT_BIN="$HOME/.local/bin/agent"' "$file" 2>/dev/null &&
47+
grep -Fq 'Install/update with: cursor-installer --update [stable|latest]' "$file" 2>/dev/null; then
4348
return 0
4449
fi
50+
4551
return 1
4652
}
4753

shell-path.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/sh
2+
# cursor-linux-installer-path
3+
4+
cursor_installer_local_bin="$HOME/.local/bin"
5+
6+
if [ -d "$cursor_installer_local_bin" ]; then
7+
cursor_installer_filtered_path=$(
8+
printf '%s' "${PATH:-}" |
9+
awk -v RS=: -v ORS=: -v skip="$cursor_installer_local_bin" '$0 != skip { print }' |
10+
sed 's/:$//'
11+
)
12+
13+
if [ -n "$cursor_installer_filtered_path" ]; then
14+
PATH="$cursor_installer_local_bin:$cursor_installer_filtered_path"
15+
else
16+
PATH="$cursor_installer_local_bin"
17+
fi
18+
19+
export PATH
20+
fi
21+
22+
unset cursor_installer_local_bin
23+
unset cursor_installer_filtered_path

0 commit comments

Comments
 (0)