feat(setup): wire --host cursor through the full install path

'./setup --host cursor' was accepted by the flag parser and then did
nothing: no INSTALL_CURSOR branch existed, so the script built binaries,
printed no 'ready' line, and installed zero skills — Cursor users had no
way to install gstack at all.

Full install slice, re-derived from PR #2547 by @szsunyuan onto the
current installers: generate .cursor/ skill docs (host config already
existed), create a minimal ~/.cursor/skills/gstack runtime root (root
SKILL.md + bin/lib/browse assets + review checklist pair + ETHOS.md +
supabase config — bin and lib travel together because bin scripts import
../lib), link the generated gstack-* skills, and plant the repo-local
.cursor/skills/gstack sidecar WITHOUT ever wiping the generated SKILL.md
files it shares a directory with (link-before-sidecar ordering keeps the
generation fallback alive). Auto mode detects Cursor via the cursor
binary or the ~/.cursor footprint. gstack-uninstall removes
~/.cursor/skills/gstack* and per-project .cursor/skills/gstack* — and
never rmdir's .cursor itself, where Cursor stores user rules.

Re-derivation deltas from the PR: the link guards carry the #2444
IS_WINDOWS bypass (re-runs refresh real-dir copies), lib/ and
supabase/config.sh ride along like every other runtime root, and the
hosts/cursor.ts sidecar field is omitted (HostConfig no longer carries
one — sidecar behavior lives in setup).

Fixes #1358

Co-authored-by: Yuan Sun <forrest.sun527@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-16 09:49:31 -07:00
co-authored by Yuan Sun Claude Fable 5
parent c84246845e
commit 2be9bd0660
5 changed files with 320 additions and 5 deletions
+170 -1
View File
@@ -58,6 +58,8 @@ FACTORY_SKILLS="$HOME/.factory/skills"
FACTORY_GSTACK="$FACTORY_SKILLS/gstack"
OPENCODE_SKILLS="$HOME/.config/opencode/skills"
OPENCODE_GSTACK="$OPENCODE_SKILLS/gstack"
CURSOR_SKILLS="$HOME/.cursor/skills"
CURSOR_GSTACK="$CURSOR_SKILLS/gstack"
IS_WINDOWS=0
case "$(uname -s)" in
@@ -239,14 +241,19 @@ INSTALL_CODEX=0
INSTALL_KIRO=0
INSTALL_FACTORY=0
INSTALL_OPENCODE=0
INSTALL_CURSOR=0
if [ "$HOST" = "auto" ]; then
command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1
command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1
command -v kiro-cli >/dev/null 2>&1 && INSTALL_KIRO=1
command -v droid >/dev/null 2>&1 && INSTALL_FACTORY=1
command -v opencode >/dev/null 2>&1 && INSTALL_OPENCODE=1
# Cursor's `cursor` CLI shim isn't always on PATH; ~/.cursor is the
# reliable footprint of an installed Cursor IDE.
command -v cursor >/dev/null 2>&1 && INSTALL_CURSOR=1
[ -d "$HOME/.cursor" ] && INSTALL_CURSOR=1
# If none found, default to claude
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ]; then
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ] && [ "$INSTALL_CURSOR" -eq 0 ]; then
INSTALL_CLAUDE=1
fi
elif [ "$HOST" = "claude" ]; then
@@ -259,6 +266,8 @@ elif [ "$HOST" = "factory" ]; then
INSTALL_FACTORY=1
elif [ "$HOST" = "opencode" ]; then
INSTALL_OPENCODE=1
elif [ "$HOST" = "cursor" ]; then
INSTALL_CURSOR=1
fi
migrate_direct_codex_install() {
@@ -593,6 +602,16 @@ if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
)
fi
# 1e. Generate .cursor/ Cursor skill docs
if [ "$INSTALL_CURSOR" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
log "Generating .cursor/ skill docs..."
(
cd "$SOURCE_GSTACK_DIR"
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
bun_cmd run gen:skill-docs --host cursor
)
fi
# 2. Ensure Playwright's Chromium is available
# Detect Ubuntu 26.04: Playwright does not yet ship a native chromium build for
# ubuntu26.04-x64. Override the platform to ubuntu24.04-x64 so the installer
@@ -1234,6 +1253,143 @@ link_opencode_skill_dirs() {
fi
}
# ─── Helper: create a minimal ~/.cursor/skills/gstack runtime root ──────────
# Cursor scans ~/.cursor/skills. Same shape as the Codex/Factory/OpenCode
# runtime roots: root SKILL.md from the generated tree + runtime assets only.
# Contributed by @szsunyuan (PR #2547), re-derived onto the current installers.
create_cursor_runtime_root() {
local gstack_dir="$1"
local cursor_gstack="$2"
local cursor_dir="$gstack_dir/.cursor/skills"
local generated_root="$cursor_dir/gstack"
if [ -L "$cursor_gstack" ]; then
rm -f "$cursor_gstack"
elif [ -d "$cursor_gstack" ] && [ "$cursor_gstack" != "$gstack_dir" ] && [ "$cursor_gstack" != "$generated_root" ]; then
rm -rf "$cursor_gstack"
fi
mkdir -p "$cursor_gstack" "$cursor_gstack/browse" "$cursor_gstack/gstack-upgrade" "$cursor_gstack/review"
if [ -f "$cursor_dir/gstack/SKILL.md" ]; then
_link_or_copy "$cursor_dir/gstack/SKILL.md" "$cursor_gstack/SKILL.md"
fi
# bin scripts import shared modules via ../lib — bin and lib travel together.
if [ -d "$gstack_dir/bin" ]; then
_link_or_copy "$gstack_dir/bin" "$cursor_gstack/bin"
fi
if [ -d "$gstack_dir/lib" ]; then
_link_or_copy "$gstack_dir/lib" "$cursor_gstack/lib"
fi
if [ -d "$gstack_dir/browse/dist" ]; then
_link_or_copy "$gstack_dir/browse/dist" "$cursor_gstack/browse/dist"
fi
if [ -d "$gstack_dir/browse/bin" ]; then
_link_or_copy "$gstack_dir/browse/bin" "$cursor_gstack/browse/bin"
fi
if [ -f "$cursor_dir/gstack-upgrade/SKILL.md" ]; then
_link_or_copy "$cursor_dir/gstack-upgrade/SKILL.md" "$cursor_gstack/gstack-upgrade/SKILL.md"
fi
# Review runtime assets — the cursor host config ships the lean pair.
for f in checklist.md TODOS-format.md; do
if [ -f "$gstack_dir/review/$f" ]; then
_link_or_copy "$gstack_dir/review/$f" "$cursor_gstack/review/$f"
fi
done
if [ -f "$gstack_dir/ETHOS.md" ]; then
_link_or_copy "$gstack_dir/ETHOS.md" "$cursor_gstack/ETHOS.md"
fi
# supabase/config.sh — required by gstack-telemetry-sync to resolve GSTACK_SUPABASE_URL
if [ -f "$gstack_dir/supabase/config.sh" ]; then
mkdir -p "$cursor_gstack/supabase"
_link_or_copy "$gstack_dir/supabase/config.sh" "$cursor_gstack/supabase/config.sh"
fi
}
# Plant runtime assets into the repo-local generated skill dir so in-repo
# GSTACK_ROOT (preamble prefers $_ROOT/.cursor/skills/gstack) has bin/.
# NEVER wipe this directory — it holds the generated SKILL.md files.
create_cursor_sidecar() {
local repo_root="$1"
local cursor_gstack="$repo_root/.cursor/skills/gstack"
local cursor_dir="$repo_root/.cursor/skills"
mkdir -p "$cursor_gstack" "$cursor_gstack/browse" "$cursor_gstack/gstack-upgrade" "$cursor_gstack/review"
if [ -d "$repo_root/bin" ]; then
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/bin" ] || [ ! -e "$cursor_gstack/bin" ]; then
_link_or_copy "$repo_root/bin" "$cursor_gstack/bin"
fi
fi
if [ -d "$repo_root/lib" ]; then
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/lib" ] || [ ! -e "$cursor_gstack/lib" ]; then
_link_or_copy "$repo_root/lib" "$cursor_gstack/lib"
fi
fi
if [ -d "$repo_root/browse/dist" ]; then
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/browse/dist" ] || [ ! -e "$cursor_gstack/browse/dist" ]; then
_link_or_copy "$repo_root/browse/dist" "$cursor_gstack/browse/dist"
fi
fi
if [ -d "$repo_root/browse/bin" ]; then
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/browse/bin" ] || [ ! -e "$cursor_gstack/browse/bin" ]; then
_link_or_copy "$repo_root/browse/bin" "$cursor_gstack/browse/bin"
fi
fi
if [ -f "$cursor_dir/gstack-upgrade/SKILL.md" ]; then
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/gstack-upgrade/SKILL.md" ] || [ ! -e "$cursor_gstack/gstack-upgrade/SKILL.md" ]; then
_link_or_copy "$cursor_dir/gstack-upgrade/SKILL.md" "$cursor_gstack/gstack-upgrade/SKILL.md"
fi
fi
for f in checklist.md TODOS-format.md; do
if [ -f "$repo_root/review/$f" ]; then
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/review/$f" ] || [ ! -e "$cursor_gstack/review/$f" ]; then
_link_or_copy "$repo_root/review/$f" "$cursor_gstack/review/$f"
fi
fi
done
if [ -f "$repo_root/ETHOS.md" ]; then
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/ETHOS.md" ] || [ ! -e "$cursor_gstack/ETHOS.md" ]; then
_link_or_copy "$repo_root/ETHOS.md" "$cursor_gstack/ETHOS.md"
fi
fi
}
link_cursor_skill_dirs() {
local gstack_dir="$1"
local skills_dir="$2"
local cursor_dir="$gstack_dir/.cursor/skills"
local linked=()
if [ ! -d "$cursor_dir" ]; then
echo " Generating .cursor/ skill docs..."
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host cursor )
fi
if [ ! -d "$cursor_dir" ]; then
echo " warning: .cursor/skills/ generation failed — run 'bun run gen:skill-docs --host cursor' manually" >&2
return 1
fi
for skill_dir in "$cursor_dir"/gstack*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
[ "$skill_name" = "gstack" ] && continue
target="$skills_dir/$skill_name"
# #2444: IS_WINDOWS bypass — real-dir copies never match -L, so re-runs
# skipped the refresh. Only replace a symlink or a missing path
# otherwise; never an unowned real Cursor skill dir (#2142).
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$target" ] || [ ! -e "$target" ]; then
_link_or_copy "$skill_dir" "$target"
linked+=("$skill_name")
fi
fi
done
if [ ${#linked[@]} -gt 0 ]; then
echo " linked skills: ${linked[*]}"
fi
}
# 4. Install for Claude (default)
SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")"
SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")"
@@ -1468,6 +1624,19 @@ if [ "$INSTALL_OPENCODE" -eq 1 ]; then
echo " opencode skills: $OPENCODE_SKILLS"
fi
# 6d. Install for Cursor
if [ "$INSTALL_CURSOR" -eq 1 ]; then
mkdir -p "$CURSOR_SKILLS"
create_cursor_runtime_root "$SOURCE_GSTACK_DIR" "$CURSOR_GSTACK"
# Link before sidecar. Sidecar mkdir -p creates .cursor/skills/gstack, which
# would make link_cursor_skill_dirs' "[ ! -d generated ]" gen fallback a no-op.
link_cursor_skill_dirs "$SOURCE_GSTACK_DIR" "$CURSOR_SKILLS"
create_cursor_sidecar "$SOURCE_GSTACK_DIR"
echo "gstack ready (cursor)."
echo " browse: $BROWSE_BIN"
echo " cursor skills: $CURSOR_SKILLS"
fi
# 7. Create .agents/ sidecar symlinks for the real Codex skill target.
# The root Codex skill ends up pointing at $SOURCE_GSTACK_DIR/.agents/skills/gstack,
# so the runtime assets must live there for both global and repo-local installs.