#!/usr/bin/env bash # gstack setup — build browser binary + register skills with Claude Code / Codex set -e if ! command -v bun >/dev/null 2>&1; then echo "Error: bun is required but not installed." >&2 echo "Install with checksum verification:" >&2 echo ' BUN_VERSION="1.3.10"' >&2 echo ' tmpfile=$(mktemp)' >&2 echo ' curl -fsSL "https://bun.sh/install" -o "$tmpfile"' >&2 echo ' echo "Verify checksum before running: shasum -a 256 $tmpfile"' >&2 echo ' BUN_VERSION="$BUN_VERSION" bash "$tmpfile" && rm "$tmpfile"' >&2 exit 1 fi INSTALL_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)" SOURCE_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd -P)" INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")" BROWSE_BIN="$SOURCE_GSTACK_DIR/browse/dist/browse" CODEX_SKILLS="$HOME/.codex/skills" CODEX_GSTACK="$CODEX_SKILLS/gstack" FACTORY_SKILLS="$HOME/.factory/skills" FACTORY_GSTACK="$FACTORY_SKILLS/gstack" IS_WINDOWS=0 case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*|Windows_NT) IS_WINDOWS=1 ;; esac # ─── Parse flags ────────────────────────────────────────────── HOST="claude" LOCAL_INSTALL=0 SKILL_PREFIX=1 SKILL_PREFIX_FLAG=0 while [ $# -gt 0 ]; do case "$1" in --host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, 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 ;; --no-prefix) SKILL_PREFIX=0; SKILL_PREFIX_FLAG=1; shift ;; *) shift ;; esac done case "$HOST" in claude|codex|kiro|factory|auto) ;; *) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, or auto)" >&2; exit 1 ;; esac # ─── Resolve skill prefix preference ───────────────────────── # Priority: CLI flag > saved config > interactive prompt (or flat default for non-TTY) GSTACK_CONFIG="$SOURCE_GSTACK_DIR/bin/gstack-config" export GSTACK_SETUP_RUNNING=1 # Prevent gstack-config post-set hook from triggering relink mid-setup if [ "$SKILL_PREFIX_FLAG" -eq 0 ]; then _saved_prefix="$("$GSTACK_CONFIG" get skill_prefix 2>/dev/null || true)" if [ "$_saved_prefix" = "true" ]; then SKILL_PREFIX=1 elif [ "$_saved_prefix" = "false" ]; then SKILL_PREFIX=0 else # No saved preference — prompt interactively (or default flat for non-TTY) if [ -t 0 ]; then echo "" echo "Skill naming: how should gstack skills appear?" echo "" echo " 1) Short names: /qa, /ship, /review" echo " Recommended. Clean and fast to type." echo "" echo " 2) Namespaced: /gstack-qa, /gstack-ship, /gstack-review" echo " Use this if you run other skill packs alongside gstack to avoid conflicts." echo "" printf "Choice [1/2] (default: 1, auto-selects in 10s): " read -t 10 -r _prefix_choice /dev/null || _prefix_choice="" case "$_prefix_choice" in 2) SKILL_PREFIX=1 ;; *) SKILL_PREFIX=0 ;; esac else SKILL_PREFIX=0 fi # Save the choice for future runs "$GSTACK_CONFIG" set skill_prefix "$([ "$SKILL_PREFIX" -eq 1 ] && echo true || echo false)" 2>/dev/null || true fi else # Flag was passed explicitly — persist the choice "$GSTACK_CONFIG" set skill_prefix "$([ "$SKILL_PREFIX" -eq 1 ] && echo true || echo false)" 2>/dev/null || true fi # --local: install to .claude/skills/ in the current working directory if [ "$LOCAL_INSTALL" -eq 1 ]; then if [ "$HOST" = "codex" ]; then echo "Error: --local is only supported for Claude Code (not Codex)." >&2 exit 1 fi INSTALL_SKILLS_DIR="$(pwd)/.claude/skills" mkdir -p "$INSTALL_SKILLS_DIR" HOST="claude" INSTALL_CODEX=0 fi # For auto: detect which agents are installed INSTALL_CLAUDE=0 INSTALL_CODEX=0 INSTALL_KIRO=0 INSTALL_FACTORY=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 # If none found, default to claude if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ]; then INSTALL_CLAUDE=1 fi elif [ "$HOST" = "claude" ]; then INSTALL_CLAUDE=1 elif [ "$HOST" = "codex" ]; then INSTALL_CODEX=1 elif [ "$HOST" = "kiro" ]; then INSTALL_KIRO=1 elif [ "$HOST" = "factory" ]; then INSTALL_FACTORY=1 fi migrate_direct_codex_install() { local gstack_dir="$1" local codex_gstack="$2" local migrated_dir="$HOME/.gstack/repos/gstack" [ "$gstack_dir" = "$codex_gstack" ] || return 0 [ -L "$gstack_dir" ] && return 0 mkdir -p "$(dirname "$migrated_dir")" if [ -e "$migrated_dir" ] && [ "$migrated_dir" != "$gstack_dir" ]; then echo "gstack setup failed: direct Codex install detected at $gstack_dir" >&2 echo "A migrated repo already exists at $migrated_dir; move one of them aside and rerun setup." >&2 exit 1 fi echo "Migrating direct Codex install to $migrated_dir to avoid duplicate skill discovery..." mv "$gstack_dir" "$migrated_dir" SOURCE_GSTACK_DIR="$migrated_dir" INSTALL_GSTACK_DIR="$migrated_dir" INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")" BROWSE_BIN="$SOURCE_GSTACK_DIR/browse/dist/browse" } if [ "$INSTALL_CODEX" -eq 1 ]; then migrate_direct_codex_install "$SOURCE_GSTACK_DIR" "$CODEX_GSTACK" fi ensure_playwright_browser() { if [ "$IS_WINDOWS" -eq 1 ]; then # On Windows, Bun can't launch Chromium due to broken pipe handling # (oven-sh/bun#4253). Use Node.js to verify Chromium works instead. ( cd "$SOURCE_GSTACK_DIR" node -e "const { chromium } = require('playwright'); (async () => { const b = await chromium.launch(); await b.close(); })()" 2>/dev/null ) else ( cd "$SOURCE_GSTACK_DIR" bun --eval 'import { chromium } from "playwright"; const browser = await chromium.launch(); await browser.close();' ) >/dev/null 2>&1 fi } # 1. Build browse binary if needed (smart rebuild: stale sources, package.json, lock) NEEDS_BUILD=0 if [ ! -x "$BROWSE_BIN" ]; then NEEDS_BUILD=1 elif [ -n "$(find "$SOURCE_GSTACK_DIR/browse/src" -type f -newer "$BROWSE_BIN" -print -quit 2>/dev/null)" ]; then NEEDS_BUILD=1 elif [ "$SOURCE_GSTACK_DIR/package.json" -nt "$BROWSE_BIN" ]; then NEEDS_BUILD=1 elif [ -f "$SOURCE_GSTACK_DIR/bun.lock" ] && [ "$SOURCE_GSTACK_DIR/bun.lock" -nt "$BROWSE_BIN" ]; then NEEDS_BUILD=1 fi if [ "$NEEDS_BUILD" -eq 1 ]; then echo "Building browse binary..." ( cd "$SOURCE_GSTACK_DIR" bun install bun run build ) # Safety net: write .version if build script didn't (e.g., git not available during build) if [ ! -f "$SOURCE_GSTACK_DIR/browse/dist/.version" ]; then git -C "$SOURCE_GSTACK_DIR" rev-parse HEAD > "$SOURCE_GSTACK_DIR/browse/dist/.version" 2>/dev/null || true fi fi if [ ! -x "$BROWSE_BIN" ]; then echo "gstack setup failed: browse binary missing at $BROWSE_BIN" >&2 exit 1 fi # 1b. Generate .agents/ Codex skill docs — always regenerate to prevent stale descriptions. # .agents/ is no longer committed — generated at setup time from .tmpl templates. # bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh). # Always regenerate: generation is fast (<2s) and mtime-based staleness checks are fragile # (miss stale files when timestamps match after clone/checkout/upgrade). AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills" NEEDS_AGENTS_GEN=1 if [ "$NEEDS_AGENTS_GEN" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then echo "Generating .agents/ skill docs..." ( cd "$SOURCE_GSTACK_DIR" bun install --frozen-lockfile 2>/dev/null || bun install bun run gen:skill-docs --host codex ) fi # 1c. Generate .factory/ Factory Droid skill docs if [ "$INSTALL_FACTORY" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then echo "Generating .factory/ skill docs..." ( cd "$SOURCE_GSTACK_DIR" bun install --frozen-lockfile 2>/dev/null || bun install bun run gen:skill-docs --host factory ) fi # 2. Ensure Playwright's Chromium is available if ! ensure_playwright_browser; then echo "Installing Playwright Chromium..." ( cd "$SOURCE_GSTACK_DIR" bunx playwright install chromium ) if [ "$IS_WINDOWS" -eq 1 ]; then # On Windows, Node.js launches Chromium (not Bun — see oven-sh/bun#4253). # Ensure playwright is importable by Node from the gstack directory. if ! command -v node >/dev/null 2>&1; then echo "gstack setup failed: Node.js is required on Windows (Bun cannot launch Chromium due to a pipe bug)" >&2 echo " Install Node.js: https://nodejs.org/" >&2 exit 1 fi echo "Windows detected — verifying Node.js can load Playwright..." ( cd "$SOURCE_GSTACK_DIR" # Bun's node_modules already has playwright; verify Node can require it node -e "require('playwright')" 2>/dev/null || npm install --no-save playwright ) fi fi if ! ensure_playwright_browser; then if [ "$IS_WINDOWS" -eq 1 ]; then echo "gstack setup failed: Playwright Chromium could not be launched via Node.js" >&2 echo " This is a known issue with Bun on Windows (oven-sh/bun#4253)." >&2 echo " Ensure Node.js is installed and 'node -e \"require('playwright')\"' works." >&2 else echo "gstack setup failed: Playwright Chromium could not be launched" >&2 fi exit 1 fi # 3. Ensure ~/.gstack global state directory exists mkdir -p "$HOME/.gstack/projects" # ─── Helper: link Claude skill subdirectories into a skills parent directory ── # When SKILL_PREFIX=1 (default), symlinks are prefixed with "gstack-" to avoid # namespace pollution (e.g., gstack-review instead of review). # Use --no-prefix to restore the old flat names. link_claude_skill_dirs() { local gstack_dir="$1" local skills_dir="$2" local linked=() for skill_dir in "$gstack_dir"/*/; do if [ -f "$skill_dir/SKILL.md" ]; then skill_name="$(basename "$skill_dir")" # Skip node_modules [ "$skill_name" = "node_modules" ] && continue # Apply gstack- prefix unless --no-prefix or already prefixed if [ "$SKILL_PREFIX" -eq 1 ]; then case "$skill_name" in gstack-*) link_name="$skill_name" ;; *) link_name="gstack-$skill_name" ;; esac else link_name="$skill_name" fi target="$skills_dir/$link_name" # Create or update symlink; skip if a real file/directory exists if [ -L "$target" ] || [ ! -e "$target" ]; then ln -snf "gstack/$skill_name" "$target" linked+=("$link_name") fi fi done if [ ${#linked[@]} -gt 0 ]; then echo " linked skills: ${linked[*]}" fi } # ─── Helper: remove old unprefixed Claude skill symlinks ────────────────────── # Migration: when switching from flat names to gstack- prefixed names, # clean up stale symlinks that point into the gstack directory. cleanup_old_claude_symlinks() { local gstack_dir="$1" local skills_dir="$2" local removed=() for skill_dir in "$gstack_dir"/*/; do if [ -f "$skill_dir/SKILL.md" ]; then skill_name="$(basename "$skill_dir")" [ "$skill_name" = "node_modules" ] && continue # Skip already-prefixed dirs (gstack-upgrade) — no old symlink to clean case "$skill_name" in gstack-*) continue ;; esac old_target="$skills_dir/$skill_name" # Only remove if it's a symlink pointing into gstack/ if [ -L "$old_target" ]; then link_dest="$(readlink "$old_target" 2>/dev/null || true)" case "$link_dest" in gstack/*|*/gstack/*) rm -f "$old_target" removed+=("$skill_name") ;; esac fi fi done if [ ${#removed[@]} -gt 0 ]; then echo " cleaned up old symlinks: ${removed[*]}" fi } # ─── Helper: remove old prefixed Claude skill symlinks ──────────────────────── # Reverse migration: when switching from gstack- prefixed names to flat names, # clean up stale gstack-* symlinks that point into the gstack directory. cleanup_prefixed_claude_symlinks() { local gstack_dir="$1" local skills_dir="$2" local removed=() for skill_dir in "$gstack_dir"/*/; do if [ -f "$skill_dir/SKILL.md" ]; then skill_name="$(basename "$skill_dir")" [ "$skill_name" = "node_modules" ] && continue # Only clean up prefixed symlinks for dirs that AREN'T already prefixed # (e.g., remove gstack-qa but NOT gstack-upgrade which is the real dir name) case "$skill_name" in gstack-*) continue ;; esac prefixed_target="$skills_dir/gstack-$skill_name" # Only remove if it's a symlink pointing into gstack/ if [ -L "$prefixed_target" ]; then link_dest="$(readlink "$prefixed_target" 2>/dev/null || true)" case "$link_dest" in gstack/*|*/gstack/*) rm -f "$prefixed_target" removed+=("gstack-$skill_name") ;; esac fi fi done if [ ${#removed[@]} -gt 0 ]; then echo " cleaned up prefixed symlinks: ${removed[*]}" fi } # ─── Helper: link generated Codex skills into a skills parent directory ── # Installs from .agents/skills/gstack-* (the generated Codex-format skills) # instead of source dirs (which have Claude paths). link_codex_skill_dirs() { local gstack_dir="$1" local skills_dir="$2" local agents_dir="$gstack_dir/.agents/skills" local linked=() if [ ! -d "$agents_dir" ]; then echo " Generating .agents/ skill docs..." ( cd "$gstack_dir" && bun run gen:skill-docs --host codex ) fi if [ ! -d "$agents_dir" ]; then echo " warning: .agents/skills/ generation failed — run 'bun run gen:skill-docs --host codex' manually" >&2 return 1 fi for skill_dir in "$agents_dir"/gstack*/; do if [ -f "$skill_dir/SKILL.md" ]; then skill_name="$(basename "$skill_dir")" # Skip the sidecar directory — it contains runtime asset symlinks (bin/, # browse/), not a skill. Linking it would overwrite the root gstack # symlink that Step 5 already pointed at the repo root. [ "$skill_name" = "gstack" ] && continue target="$skills_dir/$skill_name" # Create or update symlink if [ -L "$target" ] || [ ! -e "$target" ]; then ln -snf "$skill_dir" "$target" linked+=("$skill_name") fi fi done if [ ${#linked[@]} -gt 0 ]; then echo " linked skills: ${linked[*]}" fi } # ─── Helper: create .agents/skills/gstack/ sidecar symlinks ────────── # Codex/Gemini/Cursor read skills from .agents/skills/. We link runtime # assets (bin/, browse/dist/, review/, qa/, etc.) so skill templates can # resolve paths like $SKILL_ROOT/review/design-checklist.md. create_agents_sidecar() { local repo_root="$1" local agents_gstack="$repo_root/.agents/skills/gstack" mkdir -p "$agents_gstack" # Sidecar directories that skills reference at runtime for asset in bin browse review qa; do local src="$SOURCE_GSTACK_DIR/$asset" local dst="$agents_gstack/$asset" if [ -d "$src" ] || [ -f "$src" ]; then if [ -L "$dst" ] || [ ! -e "$dst" ]; then ln -snf "$src" "$dst" fi fi done # Sidecar files that skills reference at runtime for file in ETHOS.md; do local src="$SOURCE_GSTACK_DIR/$file" local dst="$agents_gstack/$file" if [ -f "$src" ]; then if [ -L "$dst" ] || [ ! -e "$dst" ]; then ln -snf "$src" "$dst" fi fi done } # ─── Helper: create a minimal ~/.codex/skills/gstack runtime root ─────────── # Codex scans ~/.codex/skills recursively. Exposing the whole repo here causes # duplicate skills because source SKILL.md files and generated Codex skills are # both discoverable. Keep this directory limited to runtime assets + root skill. create_codex_runtime_root() { local gstack_dir="$1" local codex_gstack="$2" local agents_dir="$gstack_dir/.agents/skills" if [ -L "$codex_gstack" ]; then rm -f "$codex_gstack" elif [ -d "$codex_gstack" ] && [ "$codex_gstack" != "$gstack_dir" ]; then # Old direct installs left a real directory here with stale source skills. # Remove it so we start fresh with only the minimal runtime assets. rm -rf "$codex_gstack" fi mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/gstack-upgrade" "$codex_gstack/review" if [ -f "$agents_dir/gstack/SKILL.md" ]; then ln -snf "$agents_dir/gstack/SKILL.md" "$codex_gstack/SKILL.md" fi if [ -d "$gstack_dir/bin" ]; then ln -snf "$gstack_dir/bin" "$codex_gstack/bin" fi if [ -d "$gstack_dir/browse/dist" ]; then ln -snf "$gstack_dir/browse/dist" "$codex_gstack/browse/dist" fi if [ -d "$gstack_dir/browse/bin" ]; then ln -snf "$gstack_dir/browse/bin" "$codex_gstack/browse/bin" fi if [ -f "$agents_dir/gstack-upgrade/SKILL.md" ]; then ln -snf "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md" fi # Review runtime assets (individual files, NOT the whole review/ dir which has SKILL.md) for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do if [ -f "$gstack_dir/review/$f" ]; then ln -snf "$gstack_dir/review/$f" "$codex_gstack/review/$f" fi done # ETHOS.md — referenced by "Search Before Building" in all skill preambles if [ -f "$gstack_dir/ETHOS.md" ]; then ln -snf "$gstack_dir/ETHOS.md" "$codex_gstack/ETHOS.md" fi } create_factory_runtime_root() { local gstack_dir="$1" local factory_gstack="$2" local factory_dir="$gstack_dir/.factory/skills" if [ -L "$factory_gstack" ]; then rm -f "$factory_gstack" elif [ -d "$factory_gstack" ] && [ "$factory_gstack" != "$gstack_dir" ]; then rm -rf "$factory_gstack" fi mkdir -p "$factory_gstack" "$factory_gstack/browse" "$factory_gstack/gstack-upgrade" "$factory_gstack/review" if [ -f "$factory_dir/gstack/SKILL.md" ]; then ln -snf "$factory_dir/gstack/SKILL.md" "$factory_gstack/SKILL.md" fi if [ -d "$gstack_dir/bin" ]; then ln -snf "$gstack_dir/bin" "$factory_gstack/bin" fi if [ -d "$gstack_dir/browse/dist" ]; then ln -snf "$gstack_dir/browse/dist" "$factory_gstack/browse/dist" fi if [ -d "$gstack_dir/browse/bin" ]; then ln -snf "$gstack_dir/browse/bin" "$factory_gstack/browse/bin" fi if [ -f "$factory_dir/gstack-upgrade/SKILL.md" ]; then ln -snf "$factory_dir/gstack-upgrade/SKILL.md" "$factory_gstack/gstack-upgrade/SKILL.md" fi for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do if [ -f "$gstack_dir/review/$f" ]; then ln -snf "$gstack_dir/review/$f" "$factory_gstack/review/$f" fi done if [ -f "$gstack_dir/ETHOS.md" ]; then ln -snf "$gstack_dir/ETHOS.md" "$factory_gstack/ETHOS.md" fi } link_factory_skill_dirs() { local gstack_dir="$1" local skills_dir="$2" local factory_dir="$gstack_dir/.factory/skills" local linked=() if [ ! -d "$factory_dir" ]; then echo " Generating .factory/ skill docs..." ( cd "$gstack_dir" && bun run gen:skill-docs --host factory ) fi if [ ! -d "$factory_dir" ]; then echo " warning: .factory/skills/ generation failed — run 'bun run gen:skill-docs --host factory' manually" >&2 return 1 fi for skill_dir in "$factory_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 ln -snf "$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")")" CODEX_REPO_LOCAL=0 if [ "$SKILLS_BASENAME" = "skills" ] && [ "$SKILLS_PARENT_BASENAME" = ".agents" ]; then CODEX_REPO_LOCAL=1 fi if [ "$INSTALL_CLAUDE" -eq 1 ]; then if [ "$SKILLS_BASENAME" = "skills" ]; then # Clean up stale symlinks from the opposite prefix mode if [ "$SKILL_PREFIX" -eq 1 ]; then cleanup_old_claude_symlinks "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR" else cleanup_prefixed_claude_symlinks "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR" fi link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR" if [ "$LOCAL_INSTALL" -eq 1 ]; then echo "gstack ready (project-local)." echo " skills: $INSTALL_SKILLS_DIR" else echo "gstack ready (claude)." fi echo " browse: $BROWSE_BIN" else echo "gstack ready (claude)." echo " browse: $BROWSE_BIN" echo " (skipped skill symlinks — not inside .claude/skills/)" fi fi # 5. Install for Codex if [ "$INSTALL_CODEX" -eq 1 ]; then if [ "$CODEX_REPO_LOCAL" -eq 1 ]; then CODEX_SKILLS="$INSTALL_SKILLS_DIR" CODEX_GSTACK="$INSTALL_GSTACK_DIR" fi mkdir -p "$CODEX_SKILLS" # Skip runtime root creation for repo-local installs — the checkout IS the runtime root. # create_codex_runtime_root would create self-referential symlinks (bin → bin, etc.). if [ "$CODEX_REPO_LOCAL" -eq 0 ]; then create_codex_runtime_root "$SOURCE_GSTACK_DIR" "$CODEX_GSTACK" fi # Install generated Codex-format skills (not Claude source dirs) link_codex_skill_dirs "$SOURCE_GSTACK_DIR" "$CODEX_SKILLS" echo "gstack ready (codex)." echo " browse: $BROWSE_BIN" echo " codex skills: $CODEX_SKILLS" fi # 6. Install for Kiro CLI (copy from .agents/skills, rewrite paths) if [ "$INSTALL_KIRO" -eq 1 ]; then KIRO_SKILLS="$HOME/.kiro/skills" AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills" mkdir -p "$KIRO_SKILLS" # Create gstack dir with symlinks for runtime assets, copy+sed for SKILL.md KIRO_GSTACK="$KIRO_SKILLS/gstack" # Remove old whole-dir symlink from previous installs [ -L "$KIRO_GSTACK" ] && rm -f "$KIRO_GSTACK" mkdir -p "$KIRO_GSTACK" "$KIRO_GSTACK/browse" "$KIRO_GSTACK/gstack-upgrade" "$KIRO_GSTACK/review" ln -snf "$SOURCE_GSTACK_DIR/bin" "$KIRO_GSTACK/bin" ln -snf "$SOURCE_GSTACK_DIR/browse/dist" "$KIRO_GSTACK/browse/dist" ln -snf "$SOURCE_GSTACK_DIR/browse/bin" "$KIRO_GSTACK/browse/bin" # ETHOS.md — referenced by "Search Before Building" in all skill preambles if [ -f "$SOURCE_GSTACK_DIR/ETHOS.md" ]; then ln -snf "$SOURCE_GSTACK_DIR/ETHOS.md" "$KIRO_GSTACK/ETHOS.md" fi # gstack-upgrade skill if [ -f "$AGENTS_DIR/gstack-upgrade/SKILL.md" ]; then ln -snf "$AGENTS_DIR/gstack-upgrade/SKILL.md" "$KIRO_GSTACK/gstack-upgrade/SKILL.md" fi # Review runtime assets (individual files, not whole dir) for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do if [ -f "$SOURCE_GSTACK_DIR/review/$f" ]; then ln -snf "$SOURCE_GSTACK_DIR/review/$f" "$KIRO_GSTACK/review/$f" fi done # Rewrite root SKILL.md paths for Kiro sed -e "s|~/.claude/skills/gstack|~/.kiro/skills/gstack|g" \ -e "s|\.claude/skills/gstack|.kiro/skills/gstack|g" \ -e "s|\.claude/skills|.kiro/skills|g" \ "$SOURCE_GSTACK_DIR/SKILL.md" > "$KIRO_GSTACK/SKILL.md" if [ ! -d "$AGENTS_DIR" ]; then echo " warning: no .agents/skills/ directory found — run 'bun run build' first" >&2 else for skill_dir in "$AGENTS_DIR"/gstack*/; do [ -f "$skill_dir/SKILL.md" ] || continue skill_name="$(basename "$skill_dir")" target_dir="$KIRO_SKILLS/$skill_name" mkdir -p "$target_dir" # Generated Codex skills use $HOME/.codex (not ~/), plus $GSTACK_ROOT variables. # Rewrite the default GSTACK_ROOT value and any remaining literal paths. sed -e 's|\$HOME/.codex/skills/gstack|$HOME/.kiro/skills/gstack|g' \ -e "s|~/.codex/skills/gstack|~/.kiro/skills/gstack|g" \ -e "s|~/.claude/skills/gstack|~/.kiro/skills/gstack|g" \ "$skill_dir/SKILL.md" > "$target_dir/SKILL.md" done echo "gstack ready (kiro)." echo " browse: $BROWSE_BIN" echo " kiro skills: $KIRO_SKILLS" fi fi # 6b. Install for Factory Droid if [ "$INSTALL_FACTORY" -eq 1 ]; then mkdir -p "$FACTORY_SKILLS" create_factory_runtime_root "$SOURCE_GSTACK_DIR" "$FACTORY_GSTACK" link_factory_skill_dirs "$SOURCE_GSTACK_DIR" "$FACTORY_SKILLS" echo "gstack ready (factory)." echo " browse: $BROWSE_BIN" echo " factory skills: $FACTORY_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. if [ "$INSTALL_CODEX" -eq 1 ]; then create_agents_sidecar "$SOURCE_GSTACK_DIR" fi # 8. First-time welcome + legacy cleanup if [ ! -f "$HOME/.gstack/.welcome-seen" ]; then echo " Welcome! Run /gstack-upgrade anytime to stay current." touch "$HOME/.gstack/.welcome-seen" fi rm -f /tmp/gstack-latest-version