fix(shell): keep Cursor on agent and Grok on grok

Grok's installer prepends ~/.grok/bin and steals ~/.local/bin/agent.
easyzsh already shims `grok`; claim `agent` for Cursor and drop the
vendor bin from PATH so a later Grok upgrade cannot shadow it.
This commit is contained in:
Frank Qing
2026-08-13 17:36:56 +08:00
parent e41d2b6e93
commit 82c6a57c3a
5 changed files with 77 additions and 6 deletions
+15
View File
@@ -27,6 +27,21 @@ if [[ -d "$ZSH_ALWAYS_ON_DIR" ]]; then
. "$f"
done
fi
# Cursor owns `agent` when installed. Grok is only ~/.local/bin/grok.
# Grok's installer also writes ~/.local/bin/agent and may prepend ~/.grok/bin.
_easyzsh_agent=
[[ -L $HOME/.local/bin/agent ]] && _easyzsh_agent=$(readlink $HOME/.local/bin/agent)
if [[ -x $HOME/.local/bin/cursor-agent ]]; then
if [[ ! -e $HOME/.local/bin/agent || $_easyzsh_agent == *'/.grok/'* ]]; then
ln -sfn $HOME/.local/bin/cursor-agent $HOME/.local/bin/agent
fi
elif [[ -L $HOME/.local/bin/agent && $_easyzsh_agent == *'/.grok/'* ]]; then
rm -f $HOME/.local/bin/agent
fi
unset _easyzsh_agent
path=(${path:#$HOME/.grok/bin})
if [[ -f "$ZSH_ALWAYS_ON_DIR/local.zsh" ]]; then
# shellcheck disable=SC1090
. "$ZSH_ALWAYS_ON_DIR/local.zsh"
+6
View File
@@ -148,3 +148,9 @@ alias j="z"
if (( $+functions[k] )); then
alias k="k -h"
fi
# Grok CLI may prepend ~/.grok/bin (its `agent` shadows Cursor).
path=(${path:#$HOME/.grok/bin})
if [[ -x $HOME/.local/bin/cursor-agent ]]; then
alias agent=$HOME/.local/bin/cursor-agent
fi
+6 -2
View File
@@ -39,13 +39,17 @@ From a git checkout (shell stubs only):
| `~/.config/zsh/*.zsh` | Always-on tool fragments (e.g. `fnm.zsh`) | Left alone (via `patch.sh`) |
| `~/.config/zsh/local.zsh` | Machine-local overrides (every zsh, including agents) | **Never overwritten** |
| `~/.config/zshrc/*.zsh` | Personal/tool overlays before oh-my-zsh (interactive) | Left alone |
| `~/.local/bin/` | User tools + shims (e.g. `grok``~/.grok/bin/grok`) | Left alone |
| `~/.local/bin/` | User tools + shims (`grok`Grok CLI; `agent` → Cursor when installed) | Left alone except those two shims |
Core deploy does not manage `~/.zprofile`. The pyenv add-on appends one login
hook so macOS `path_helper` cannot outrank pyenv shims. Put other machine hooks
in `local.zsh`.
Do **not** put `~/.grok/bin` on PATH wholesale.
Do **not** put `~/.grok/bin` on PATH. That tree ships an `agent` that shadows
Cursor. Grok is the `grok` command (`~/.local/bin/grok`). Cursor is `agent`
and `cursor-agent`. Deploy and `.zshenv` retarget a Grok-stolen
`~/.local/bin/agent`; `.zshrc` drops `~/.grok/bin` again if a Grok upgrade
prepends it.
```bash
# ~/.config/zsh/local.zsh
+18 -2
View File
@@ -154,11 +154,27 @@ install -m 644 "${ROOT}/.zshrc" "${TARGET_HOME}/.zshrc"
"${ZSH_CFG}/local.zsh.example" \
"${TARGET_HOME}/.config/zshenv.local"
# Ensure intentional grok shim when the binary tree exists.
# Grok is `grok`. Cursor owns `agent` when its CLI is present.
if [[ -e "${TARGET_HOME}/.grok/bin/grok" ]]; then
ln -sfn "${TARGET_HOME}/.grok/bin/grok" "${TARGET_HOME}/.local/bin/grok"
echo "Ensured ~/.local/bin/grok -> ~/.grok/bin/grok"
fi
agent_link="${TARGET_HOME}/.local/bin/agent"
cursor_agent="${TARGET_HOME}/.local/bin/cursor-agent"
agent_target=""
if [[ -L "$agent_link" ]]; then
agent_target=$(readlink "$agent_link")
fi
if [[ -x "$cursor_agent" ]]; then
if [[ ! -e "$agent_link" || "$agent_target" == *'/.grok/'* ]]; then
ln -sfn "$cursor_agent" "$agent_link"
echo "Ensured ~/.local/bin/agent -> cursor-agent"
fi
elif [[ -L "$agent_link" && "$agent_target" == *'/.grok/'* ]]; then
rm -f "$agent_link"
echo "Removed ~/.local/bin/agent (was Grok; Cursor is not installed)"
fi
echo "Deploy complete."
echo "Verify: env -i HOME=\"$TARGET_HOME\" PATH=/usr/bin:/bin zsh -c '. ~/.zshenv; command -v grok'"
echo "Verify: env -i HOME=\"$TARGET_HOME\" PATH=/usr/bin:/bin zsh -c '. ~/.zshenv; command -v grok; command -v agent'"
+32 -2
View File
@@ -113,6 +113,12 @@ fi
grep -Fq 'brew shellenv' "$ROOT/.zshenv" || fail "Homebrew is not initialized in .zshenv"
grep -Fq '/usr/local/bin/brew' "$ROOT/.zshenv" || fail "Intel Homebrew path is missing from .zshenv"
grep -Fq '$HOME/.local/bin' "$ROOT/.zshenv" || fail "~/.local/bin is not added in .zshenv"
grep -Fq '${path:#$HOME/.grok/bin}' "$ROOT/.zshenv" || fail ".zshenv does not drop ~/.grok/bin"
grep -Fq '${path:#$HOME/.grok/bin}' "$ROOT/.zshrc" || fail ".zshrc does not drop ~/.grok/bin"
grep -Fq 'alias agent=$HOME/.local/bin/cursor-agent' "$ROOT/.zshrc" || fail ".zshrc does not alias agent to Cursor"
if grep -Eq '^[[:space:]]*(export[[:space:]]+)?PATH=.*\.grok/bin' "$ROOT/.zshenv" "$ROOT/.zshrc"; then
fail "easyzsh stubs must not prepend ~/.grok/bin"
fi
grep -Fq 'local.zsh' "$ROOT/.zshenv" || fail "local.zsh is not sourced from .zshenv"
grep -Fq 'ZSH_ALWAYS_ON_DIR' "$ROOT/.zshenv" || fail "always-on ~/.config/zsh/*.zsh glob is missing from .zshenv"
grep -Fq 'zsh/fnm' "$ROOT/install_fnm.sh" || fail "install_fnm.sh does not patch always-on zsh/fnm"
@@ -130,19 +136,43 @@ fi
# deploy.sh installs stubs and preserves local.zsh; removes obsolete tree
deploy_home="$TEMP_DIR/deploy-home"
mkdir -p "$deploy_home/.config/zsh/env" "$deploy_home/.local/bin" "$deploy_home/.grok/bin"
printf '%s\n' 'machine-only' >"$deploy_home/.config/zsh/local.zsh"
printf '%s\n' '# machine-only' >"$deploy_home/.config/zsh/local.zsh"
printf '%s\n' 'stale' >"$deploy_home/.config/zsh/env/00-path.zsh"
printf '%s\n' '#!/bin/sh' 'echo grok-ok' >"$deploy_home/.grok/bin/grok"
chmod +x "$deploy_home/.grok/bin/grok"
HOME="$deploy_home" bash "$ROOT/deploy.sh" --home "$deploy_home"
[[ -f "$deploy_home/.zshenv" ]] || fail "deploy.sh did not install .zshenv"
[[ -f "$deploy_home/.zshrc" ]] || fail "deploy.sh did not install .zshrc"
grep -Fxq 'machine-only' "$deploy_home/.config/zsh/local.zsh" ||
grep -Fxq '# machine-only' "$deploy_home/.config/zsh/local.zsh" ||
fail "deploy.sh overwrote local.zsh"
[[ ! -e "$deploy_home/.config/zsh/env" ]] || fail "deploy.sh left obsolete env/ tree"
[[ ! -e "$deploy_home/.config/zsh/local.zsh.example" ]] || fail "deploy.sh left local.zsh.example in home"
[[ -L "$deploy_home/.local/bin/grok" ]] || fail "deploy.sh did not create grok shim"
# Grok's `agent` must not keep the name when Cursor is absent.
printf '%s\n' '#!/bin/sh' 'echo grok-agent' >"$deploy_home/.grok/bin/agent"
chmod +x "$deploy_home/.grok/bin/agent"
ln -sfn "$deploy_home/.grok/bin/agent" "$deploy_home/.local/bin/agent"
HOME="$deploy_home" bash "$ROOT/deploy.sh" --home "$deploy_home" >/dev/null
[[ ! -e "$deploy_home/.local/bin/agent" ]] || fail "deploy.sh left Grok bound to agent without Cursor"
# Cursor takes `agent`; Grok stays on `grok`.
printf '%s\n' '#!/bin/sh' 'echo cursor-agent' >"$deploy_home/.local/bin/cursor-agent"
chmod +x "$deploy_home/.local/bin/cursor-agent"
ln -sfn "$deploy_home/.grok/bin/agent" "$deploy_home/.local/bin/agent"
HOME="$deploy_home" bash "$ROOT/deploy.sh" --home "$deploy_home" >/dev/null
[[ "$(readlink "$deploy_home/.local/bin/agent")" == "$deploy_home/.local/bin/cursor-agent" ]] ||
fail "deploy.sh did not point agent at cursor-agent"
[[ "$(readlink "$deploy_home/.local/bin/grok")" == "$deploy_home/.grok/bin/grok" ]] ||
fail "deploy.sh did not keep the grok shim"
# .zshenv repairs a Grok-stolen agent without deploy.
ln -sfn "$deploy_home/.grok/bin/agent" "$deploy_home/.local/bin/agent"
HOME="$deploy_home" PATH=/usr/bin:/bin zsh -c '. ~/.zshenv' ||
fail ".zshenv failed while repairing agent"
[[ "$(readlink "$deploy_home/.local/bin/agent")" == "$deploy_home/.local/bin/cursor-agent" ]] ||
fail ".zshenv did not retarget a Grok-stolen agent"
# Non-interactive zsh must resolve ~/.local/bin without sourcing .zshrc.
local_bin_home="$TEMP_DIR/local-bin-home"
mkdir -p "$local_bin_home/.local/bin"