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
+10 -3
View File
@@ -34,16 +34,23 @@ function extractFn(name: string): string {
const WINDOWS_BYPASS = '[ "$IS_WINDOWS" -eq 1 ] || [ -L ';
describe('setup: Windows re-run refresh — static guard sites (#2444)', () => {
test('all five install guards carry the IS_WINDOWS bypass', () => {
const sites = SETUP_SRC.split(WINDOWS_BYPASS).length - 1;
expect(sites).toBe(5);
test('no install guard is missing the IS_WINDOWS bypass', () => {
// Every `[ -L ...] || [ ! -e ...]` refresh guard in setup must carry the
// bypass — a bare guard is a Windows re-run no-op waiting to happen.
const bareGuards = SETUP_SRC
.split('\n')
.filter((l) => /\[ -L "\$[A-Za-z_/${}.]+" \] \|\| \[ ! -e /.test(l) && !l.includes('IS_WINDOWS'));
expect(bareGuards).toEqual([]);
expect(SETUP_SRC.split(WINDOWS_BYPASS).length - 1).toBeGreaterThanOrEqual(5);
});
test.each([
'link_codex_skill_dirs',
'link_factory_skill_dirs',
'link_opencode_skill_dirs',
'link_cursor_skill_dirs',
'create_agents_sidecar',
'create_cursor_sidecar',
])('%s bypasses the symlink-or-missing guard on Windows', (fn) => {
expect(extractFn(fn)).toContain(WINDOWS_BYPASS);
});