Skip to content
Open
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
104 changes: 100 additions & 4 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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
Expand Down Expand Up @@ -84,7 +86,7 @@ TEAM_MODE=0
NO_TEAM_MODE=0
while [ $# -gt 0 ]; do
case "$1" in
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, cursor, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
--host=*) HOST="${1#--host=}"; shift ;;
--local) LOCAL_INSTALL=1; shift ;;
--prefix) SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1; shift ;;
Expand All @@ -97,7 +99,7 @@ while [ $# -gt 0 ]; do
done

case "$HOST" in
claude|codex|kiro|factory|opencode|auto) ;;
claude|codex|kiro|factory|opencode|cursor|auto) ;;
openclaw)
echo ""
echo "OpenClaw integration uses a different model — OpenClaw spawns Claude Code"
Expand Down Expand Up @@ -132,7 +134,7 @@ case "$HOST" in
echo "GBrain setup and brain skills ship from the GBrain repo."
echo ""
exit 0 ;;
*) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;;
*) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, cursor, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;;
esac

# ─── Resolve skill prefix preference ─────────────────────────
Expand Down Expand Up @@ -196,14 +198,16 @@ 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
command -v cursor >/dev/null 2>&1 && 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
Expand All @@ -216,6 +220,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() {
Expand Down Expand Up @@ -362,6 +368,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 install --frozen-lockfile 2>/dev/null || bun install
bun run gen:skill-docs --host cursor
)
fi

# 2. Ensure Playwright's Chromium is available
if ! ensure_playwright_browser; then
echo "Installing Playwright Chromium..."
Expand Down Expand Up @@ -754,6 +770,44 @@ create_opencode_runtime_root() {
fi
}

create_cursor_runtime_root() {
local gstack_dir="$1"
local cursor_gstack="$2"
local cursor_dir="$gstack_dir/.cursor/skills"

if [ -L "$cursor_gstack" ]; then
rm -f "$cursor_gstack"
elif [ -d "$cursor_gstack" ] && [ "$cursor_gstack" != "$gstack_dir" ]; 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
if [ -d "$gstack_dir/bin" ]; then
_link_or_copy "$gstack_dir/bin" "$cursor_gstack/bin"
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
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
}

link_factory_skill_dirs() {
local gstack_dir="$1"
local skills_dir="$2"
Expand Down Expand Up @@ -818,6 +872,38 @@ link_opencode_skill_dirs() {
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 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"
if [ -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")")"
Expand Down Expand Up @@ -1023,6 +1109,16 @@ 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_cursor_skill_dirs "$SOURCE_GSTACK_DIR" "$CURSOR_SKILLS"
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.
Expand Down
20 changes: 17 additions & 3 deletions test/gen-skill-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2273,16 +2273,17 @@ describe('setup script validation', () => {
expect(fnBody).toContain('rm -f "$target"');
});

test('setup supports --host auto|claude|codex|kiro|opencode', () => {
test('setup supports --host auto|claude|codex|kiro|opencode|cursor', () => {
expect(setupContent).toContain('--host');
expect(setupContent).toContain('claude|codex|kiro|factory|opencode|auto');
expect(setupContent).toContain('claude|codex|kiro|factory|opencode|cursor|auto');
});

test('auto mode detects claude, codex, kiro, and opencode binaries', () => {
test('auto mode detects claude, codex, kiro, opencode, and cursor binaries', () => {
expect(setupContent).toContain('command -v claude');
expect(setupContent).toContain('command -v codex');
expect(setupContent).toContain('command -v kiro-cli');
expect(setupContent).toContain('command -v opencode');
expect(setupContent).toContain('command -v cursor');
});

// T1: Sidecar skip guard — prevents .agents/skills/gstack from being linked as a skill
Expand Down Expand Up @@ -2324,6 +2325,19 @@ describe('setup script validation', () => {
expect(setupContent).toContain('dx-hall-of-fame.md');
});

test('setup supports --host cursor with install section and Cursor skill path vars', () => {
expect(setupContent).toContain('INSTALL_CURSOR=');
expect(setupContent).toContain('CURSOR_SKILLS="$HOME/.cursor/skills"');
expect(setupContent).toContain('CURSOR_GSTACK="$CURSOR_SKILLS/gstack"');
});

test('setup installs Cursor skills into a nested gstack runtime root', () => {
expect(setupContent).toContain('create_cursor_runtime_root');
expect(setupContent).toContain('.cursor/skills');
expect(setupContent).toContain('link_cursor_skill_dirs');
expect(setupContent).toContain('bun run gen:skill-docs --host cursor');
});

test('create_agents_sidecar links runtime assets', () => {
// Sidecar must link bin, browse, review, qa
const fnStart = setupContent.indexOf('create_agents_sidecar()');
Expand Down
Loading