diff --git a/setup b/setup index 275236cd3..3dddb1f81 100755 --- a/setup +++ b/setup @@ -3,6 +3,40 @@ set -e umask 077 # Restrict new files to owner-only (0o600 files, 0o700 dirs) +usage() { + cat <<'EOF' +gstack setup — install gstack skills + build browse binary + +Usage: ./setup [options] + +Options: + --host Install for a specific host (claude, codex, kiro, factory, + opencode, openclaw, hermes, gbrain, auto). Default: claude. + --prefix Install skills with the gstack- prefix (e.g. /gstack-review). + --no-prefix Install skills with short names (e.g. /review). Default. + --team Switch to team mode (per-repo gstack with auto-update). + --no-team Force solo install even if a team-mode repo is detected. + -q, --quiet Suppress progress output. + -h, --help Show this help and exit. + +Examples: + ./setup # solo install for Claude Code + ./setup --host codex # install for OpenAI Codex CLI + ./setup --team # team mode for a shared repo + ./setup --no-prefix # use short slash-command names + +Docs: https://github.com/garrytan/gstack +EOF +} + +# Short-circuit on -h/--help before any environment checks so users can +# discover flags even without bun installed. +for _arg in "$@"; do + case "$_arg" in + -h|--help) usage; exit 0 ;; + esac +done + if ! command -v bun >/dev/null 2>&1; then echo "Error: bun is required but not installed." >&2 echo "Install with checksum verification:" >&2 diff --git a/test/setup-help.test.ts b/test/setup-help.test.ts new file mode 100644 index 000000000..f1cbcc6e3 --- /dev/null +++ b/test/setup-help.test.ts @@ -0,0 +1,64 @@ +import { describe, test, expect } from 'bun:test'; +import { spawnSync } from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const SETUP_SCRIPT = path.join(ROOT, 'setup'); + +describe('setup: --help flag (#1133)', () => { + test('setup script defines a usage() function', () => { + const content = fs.readFileSync(SETUP_SCRIPT, 'utf-8'); + expect(content).toMatch(/^usage\(\)\s*\{/m); + }); + + test('setup script short-circuits on -h/--help before env checks', () => { + const content = fs.readFileSync(SETUP_SCRIPT, 'utf-8'); + const helpIdx = content.search(/-h\|--help\)\s*usage;\s*exit 0/); + const bunCheckIdx = content.indexOf('command -v bun'); + expect(helpIdx).toBeGreaterThan(-1); + expect(bunCheckIdx).toBeGreaterThan(-1); + // --help must be handled before the bun availability check so the flag + // works on machines that haven't installed bun yet. + expect(helpIdx).toBeLessThan(bunCheckIdx); + }); + + test('usage text documents every supported flag', () => { + const content = fs.readFileSync(SETUP_SCRIPT, 'utf-8'); + const usageMatch = content.match(/usage\(\)\s*\{[\s\S]*?\n\}/); + expect(usageMatch).toBeTruthy(); + const usage = usageMatch![0]; + for (const flag of [ + '--host', + '--prefix', + '--no-prefix', + '--team', + '--no-team', + '--quiet', + '--help', + ]) { + expect(usage).toContain(flag); + } + }); + + test('./setup --help exits 0, prints usage, and does not run installer', () => { + const res = spawnSync('bash', [SETUP_SCRIPT, '--help'], { + encoding: 'utf-8', + timeout: 5000, + }); + expect(res.status).toBe(0); + expect(res.stdout).toContain('Usage:'); + expect(res.stdout).toContain('gstack setup'); + // Hard guarantee it short-circuited — none of the install-side output appears. + expect(res.stdout).not.toMatch(/Installing|bun install|Building|gen:skill-docs/); + }); + + test('./setup -h is equivalent to ./setup --help', () => { + const res = spawnSync('bash', [SETUP_SCRIPT, '-h'], { + encoding: 'utf-8', + timeout: 5000, + }); + expect(res.status).toBe(0); + expect(res.stdout).toContain('Usage:'); + }); +});