forked from garrytan/gstack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup
More file actions
executable file
·222 lines (198 loc) · 6.73 KB
/
setup
File metadata and controls
executable file
·222 lines (198 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/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 it: curl -fsSL https://bun.sh/install | bash" >&2
exit 1
fi
GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILLS_DIR="$(dirname "$GSTACK_DIR")"
BROWSE_BIN="$GSTACK_DIR/browse/dist/browse"
# ─── Parse --host flag ─────────────────────────────────────────
HOST="claude"
while [ $# -gt 0 ]; do
case "$1" in
--host) HOST="$2"; shift 2 ;;
--host=*) HOST="${1#--host=}"; shift ;;
*) shift ;;
esac
done
case "$HOST" in
claude|codex|auto) ;;
*) echo "Unknown --host value: $HOST (expected claude, codex, or auto)" >&2; exit 1 ;;
esac
# For auto: detect which agents are installed
INSTALL_CLAUDE=0
INSTALL_CODEX=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
# If neither found, default to claude
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ]; then
INSTALL_CLAUDE=1
fi
elif [ "$HOST" = "claude" ]; then
INSTALL_CLAUDE=1
elif [ "$HOST" = "codex" ]; then
INSTALL_CODEX=1
fi
ensure_playwright_browser() {
(
cd "$GSTACK_DIR"
bun --eval 'import { chromium } from "playwright"; const browser = await chromium.launch(); await browser.close();'
) >/dev/null 2>&1
}
# 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 "$GSTACK_DIR/browse/src" -type f -newer "$BROWSE_BIN" -print -quit 2>/dev/null)" ]; then
NEEDS_BUILD=1
elif [ "$GSTACK_DIR/package.json" -nt "$BROWSE_BIN" ]; then
NEEDS_BUILD=1
elif [ -f "$GSTACK_DIR/bun.lock" ] && [ "$GSTACK_DIR/bun.lock" -nt "$BROWSE_BIN" ]; then
NEEDS_BUILD=1
fi
if [ "$NEEDS_BUILD" -eq 1 ]; then
echo "Building browse binary..."
(
cd "$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 "$GSTACK_DIR/browse/dist/.version" ]; then
git -C "$GSTACK_DIR" rev-parse HEAD > "$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
# 2. Ensure Playwright's Chromium is available
if ! ensure_playwright_browser; then
echo "Installing Playwright Chromium..."
(
cd "$GSTACK_DIR"
bunx playwright install chromium
)
fi
if ! ensure_playwright_browser; then
echo "gstack setup failed: Playwright Chromium could not be launched" >&2
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 ──
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
target="$skills_dir/$skill_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+=("$skill_name")
fi
fi
done
if [ ${#linked[@]} -gt 0 ]; then
echo " linked skills: ${linked[*]}"
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 " warning: no .agents/skills/ directory found — run 'bun run build' first" >&2
return 1
fi
for skill_dir in "$agents_dir"/gstack*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
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="$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
}
# 4. Install for Claude (default)
SKILLS_BASENAME="$(basename "$SKILLS_DIR")"
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
if [ "$SKILLS_BASENAME" = "skills" ]; then
link_claude_skill_dirs "$GSTACK_DIR" "$SKILLS_DIR"
echo "gstack ready (claude)."
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
CODEX_SKILLS="$HOME/.codex/skills"
CODEX_GSTACK="$CODEX_SKILLS/gstack"
mkdir -p "$CODEX_SKILLS"
# Symlink gstack source for runtime assets (bin/, browse/dist/)
if [ -L "$CODEX_GSTACK" ] || [ ! -e "$CODEX_GSTACK" ]; then
ln -snf "$GSTACK_DIR" "$CODEX_GSTACK"
fi
# Install generated Codex-format skills (not Claude source dirs)
link_codex_skill_dirs "$GSTACK_DIR" "$CODEX_SKILLS"
echo "gstack ready (codex)."
echo " browse: $BROWSE_BIN"
echo " codex skills: $CODEX_SKILLS"
fi
# 6. Create .agents/ sidecar symlinks (useful for Codex/Gemini/Cursor workspace-local)
if [ "$INSTALL_CODEX" -eq 1 ]; then
# Detect repo root: if we're inside a skills directory, go up two levels
if [ "$SKILLS_BASENAME" = "skills" ]; then
REPO_ROOT="$(dirname "$SKILLS_DIR")"
else
REPO_ROOT="$GSTACK_DIR"
fi
create_agents_sidecar "$REPO_ROOT"
fi
# 7. First-time welcome + legacy cleanup
if [ ! -d "$HOME/.gstack" ]; then
mkdir -p "$HOME/.gstack"
echo " Welcome! Run /gstack-upgrade anytime to stay current."
fi
rm -f /tmp/gstack-latest-version