From 82c6a57c3a053e380dda1388f0b6e28e1e80245c Mon Sep 17 00:00:00 2001 From: Frank Qing Date: Thu, 13 Aug 2026 17:36:56 +0800 Subject: [PATCH] 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. --- .zshenv | 15 +++++++++++++++ .zshrc | 6 ++++++ README.md | 8 ++++++-- deploy.sh | 20 ++++++++++++++++++-- tests/installers.sh | 34 ++++++++++++++++++++++++++++++++-- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/.zshenv b/.zshenv index dd783dc..529814d 100644 --- a/.zshenv +++ b/.zshenv @@ -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" diff --git a/.zshrc b/.zshrc index 6e94455..757b516 100644 --- a/.zshrc +++ b/.zshrc @@ -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 diff --git a/README.md b/README.md index da0572b..227761d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/deploy.sh b/deploy.sh index 3a5f6d4..2cab472 100755 --- a/deploy.sh +++ b/deploy.sh @@ -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'" diff --git a/tests/installers.sh b/tests/installers.sh index bbb4db1..f2b89d4 100755 --- a/tests/installers.sh +++ b/tests/installers.sh @@ -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"