#!/usr/bin/env bash # gstack-relink — re-create skill symlinks based on skill_prefix config # # Usage: # gstack-relink # # Env overrides (for testing): # GSTACK_STATE_DIR — override ~/.gstack state directory # GSTACK_INSTALL_DIR — override gstack install directory # GSTACK_SKILLS_DIR — override target skills directory set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" GSTACK_CONFIG="${SCRIPT_DIR}/gstack-config" # Detect install dir INSTALL_DIR="${GSTACK_INSTALL_DIR:-}" if [ -z "$INSTALL_DIR" ]; then if [ -d "$HOME/.claude/skills/gstack" ]; then INSTALL_DIR="$HOME/.claude/skills/gstack" elif [ -d "${SCRIPT_DIR}/.." ] && [ -f "${SCRIPT_DIR}/../setup" ]; then INSTALL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" fi fi if [ -z "$INSTALL_DIR" ] || [ ! -d "$INSTALL_DIR" ]; then echo "Error: gstack install directory not found." >&2 echo "Run: cd ~/.claude/skills/gstack && ./setup" >&2 exit 1 fi # Detect target skills dir SKILLS_DIR="${GSTACK_SKILLS_DIR:-$(dirname "$INSTALL_DIR")}" [ -d "$SKILLS_DIR" ] || mkdir -p "$SKILLS_DIR" # Read prefix setting PREFIX=$("$GSTACK_CONFIG" get skill_prefix 2>/dev/null || echo "false") # Marker written into generated alias skills so a later relink can recognize # (and clean up) an alias it created earlier, even after a prefix flip. ALIAS_MARKER='' # Helper: remove old skill entry (symlink or real directory with symlinked SKILL.md) _cleanup_skill_entry() { local entry="$1" if [ -L "$entry" ]; then rm -f "$entry" elif [ -d "$entry" ] && [ -L "$entry/SKILL.md" ]; then rm -rf "$entry" elif [ -d "$entry" ] && [ -f "$entry/SKILL.md" ] && grep -qF "$ALIAS_MARKER" "$entry/SKILL.md" 2>/dev/null; then # A generated alias holds a real SKILL.md, not a symlink, so the branch # above never matches it. Without this, a prefix flip leaves the old alias # behind and both names stay registered. rm -rf "$entry" fi } _link_root_skill_alias() { local target="$SKILLS_DIR/_gstack-command" [ -f "$INSTALL_DIR/SKILL.md" ] || return 0 [ -L "$target" ] && rm -f "$target" mkdir -p "$target" ln -snf "$INSTALL_DIR/SKILL.md" "$target/SKILL.md" } # Backwards-compat alias: /connect-chrome → /open-gstack-browser. # # The alias must NOT be a symlink to the canonical skill directory. Claude Code # keys skills on the frontmatter `name:`, so two directories sharing a name # shadow each other and the survivor follows unsorted readdir order — meaning # `/open-gstack-browser` (the name README, AGENTS.md, the router SKILL.md and # the sidebar extension all tell users to run) would be registered on some # machines and silently missing on others. Generate a thin alias skill with its # own `name:` instead, so both commands resolve, on every machine. _link_alias_skill() { local canonical="$1" local alias_name="$2" [ -f "$INSTALL_DIR/$canonical/SKILL.md" ] || return 0 local description description=$(grep -m1 '^description:' "$INSTALL_DIR/$canonical/SKILL.md" 2>/dev/null | sed 's/^description:[[:space:]]*//') [ -n "$description" ] || description="Alias for /$canonical." # Drop the alias entry belonging to the other prefix mode, so flipping # skill_prefix never leaves two alias skills registered at once. if [ "$PREFIX" = "true" ]; then _cleanup_skill_entry "$SKILLS_DIR/$alias_name" canonical="gstack-$canonical" alias_name="gstack-$alias_name" else _cleanup_skill_entry "$SKILLS_DIR/gstack-$alias_name" fi local target="$SKILLS_DIR/$alias_name" if [ -L "$target" ]; then rm -f "$target"; fi mkdir -p "$target" if [ -L "$target/SKILL.md" ]; then rm -f "$target/SKILL.md"; fi cat > "$target/SKILL.md" <