diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 000000000..9c89c2dcc --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,21 @@ +{ + "name": "gstack-marketplace", + "owner": { + "name": "Garry Tan", + "email": "garry@ycombinator.com" + }, + "metadata": { + "description": "Garry's Stack — workflow skills for Claude Code. Plan review, code review, shipping, browser automation, and retrospectives.", + "version": "0.0.2" + }, + "plugins": [ + { + "name": "gstack", + "description": "Complete gstack bundle with all workflow skills: /plan-ceo-review (founder mode), /plan-eng-review (eng manager), /review (paranoid engineer), /ship (release automation), /browse (headless browser QA), /retro (weekly retrospective).", + "version": "0.0.2", + "homepage": "https://github.com/garrytan/gstack", + "license": "MIT", + "source": "./" + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 000000000..1ebd1b4b7 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "gstack", + "description": "Complete gstack bundle with all workflow skills: /plan-ceo-review (founder mode), /plan-eng-review (eng manager), /review (paranoid engineer), /ship (release automation), /browse (headless browser QA), /retro (weekly retrospective).", + "version": "0.0.2", + "author": { + "name": "Garry Tan", + "email": "garry@ycombinator.com" + }, + "homepage": "https://github.com/garrytan/gstack", + "repository": "https://github.com/garrytan/gstack", + "license": "MIT", + "keywords": ["workflow", "planning", "review", "shipping", "browser", "qa", "retrospective"] +} diff --git a/.gitignore b/.gitignore index 2d3e8509d..627b59bc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules/ -browse/dist/ +skills/browse/dist/ /tmp/ *.log bun.lock diff --git a/CLAUDE.md b/CLAUDE.md index 0fb4879ce..6d91ba430 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,33 +6,32 @@ bun install # install dependencies bun test # run integration tests (browse + snapshot) bun run dev # run CLI in dev mode, e.g. bun run dev goto https://example.com -bun run build # compile binary to browse/dist/browse +bun run build # compile binary to skills/browse/dist/browse ``` ## Project structure ``` gstack/ -├── browse/ # Headless browser CLI (Playwright) -│ ├── src/ # CLI + server + commands -│ ├── test/ # Integration tests + fixtures -│ └── dist/ # Compiled binary -├── ship/ # Ship workflow skill -├── review/ # PR review skill -├── plan-ceo-review/ # /plan-ceo-review skill -├── plan-eng-review/ # /plan-eng-review skill -├── retro/ # Retrospective skill -├── setup # One-time setup: build binary + symlink skills -├── SKILL.md # Browse skill (Claude discovers this) +├── .claude-plugin/ # Plugin marketplace config +│ ├── plugin.json # Plugin manifest +│ └── marketplace.json +├── skills/ # All skills (auto-discovered by plugin system) +│ ├── browse/ # Headless browser CLI (Playwright) +│ │ ├── src/ # CLI + server + commands +│ │ ├── test/ # Integration tests + fixtures +│ │ └── dist/ # Compiled binary +│ ├── ship/ # Ship workflow skill +│ ├── review/ # PR review skill +│ ├── plan-ceo-review/ # /plan-ceo-review skill +│ ├── plan-eng-review/ # /plan-eng-review skill +│ └── retro/ # Retrospective skill └── package.json # Build scripts for browse ``` -## Deploying to the active skill +## Installation -The active skill lives at `~/.claude/skills/gstack/`. After making changes: - -1. Push your branch -2. Fetch and reset in the skill directory: `cd ~/.claude/skills/gstack && git fetch origin && git reset --hard origin/main` -3. Rebuild: `cd ~/.claude/skills/gstack && bun run build` - -Or copy the binary directly: `cp browse/dist/browse ~/.claude/skills/gstack/browse/dist/browse` +``` +/plugin marketplace add garrytan/gstack +/plugin install gstack@gstack-marketplace +``` diff --git a/SKILL.md b/SKILL.md deleted file mode 100644 index 08ad3e932..000000000 --- a/SKILL.md +++ /dev/null @@ -1,254 +0,0 @@ ---- -name: gstack -version: 1.0.0 -description: | - Fast web browsing for Claude Code via persistent headless Chromium daemon. Navigate to any URL, - read page content, click elements, fill forms, run JavaScript, take screenshots, - inspect CSS/DOM, capture console/network logs, and more. ~100ms per command after - first call. Use when you need to check a website, verify a deployment, read docs, - or interact with any web page. No MCP, no Chrome extension — just fast CLI. -allowed-tools: - - Bash - - Read - ---- - -# gstack: Persistent Browser for Claude Code - -Persistent headless Chromium daemon. First call auto-starts the server (~3s). -Every subsequent call: ~100-200ms. Auto-shuts down after 30 min idle. - -## SETUP (run this check BEFORE any browse command) - -Before using any browse command, find the skill and check if the binary exists: - -```bash -# Check project-level first, then user-level -if test -x .claude/skills/gstack/browse/dist/browse; then - echo "READY_PROJECT" -elif test -x ~/.claude/skills/gstack/browse/dist/browse; then - echo "READY_USER" -else - echo "NEEDS_SETUP" -fi -``` - -Set `B` to whichever path is READY and use it for all commands. Prefer project-level if both exist. - -If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait for their response. -2. If they approve, determine the skill directory (project-level `.claude/skills/gstack` or user-level `~/.claude/skills/gstack`) and run: -```bash -cd && ./setup -``` -3. If `bun` is not installed, tell the user to install it: `curl -fsSL https://bun.sh/install | bash` -4. Verify the `.gitignore` in the skill directory contains `browse/dist/` and `node_modules/`. If either line is missing, add it. - -Once setup is done, it never needs to run again (the compiled binary persists). - -## IMPORTANT - -- Use the compiled binary via Bash: `.claude/skills/gstack/browse/dist/browse` (project) or `~/.claude/skills/gstack/browse/dist/browse` (user). -- NEVER use `mcp__claude-in-chrome__*` tools. They are slow and unreliable. -- The browser persists between calls — cookies, tabs, and state carry over. -- The server auto-starts on first command. No setup needed. - -## Quick Reference - -```bash -B=~/.claude/skills/gstack/browse/dist/browse - -# Navigate to a page -$B goto https://example.com - -# Read cleaned page text -$B text - -# Take a screenshot (then Read the image) -$B screenshot /tmp/page.png - -# Snapshot: accessibility tree with refs -$B snapshot -i - -# Click by ref (after snapshot) -$B click @e3 - -# Fill by ref -$B fill @e4 "test@test.com" - -# Run JavaScript -$B js "document.title" - -# Get all links -$B links - -# Click by CSS selector -$B click "button.submit" - -# Fill a form by CSS selector -$B fill "#email" "test@test.com" -$B fill "#password" "abc123" -$B click "button[type=submit]" - -# Get HTML of an element -$B html "main" - -# Get computed CSS -$B css "body" "font-family" - -# Get element attributes -$B attrs "nav" - -# Wait for element to appear -$B wait ".loaded" - -# Accessibility tree -$B accessibility - -# Set viewport -$B viewport 375x812 - -# Set cookies / headers -$B cookie "session=abc123" -$B header "Authorization:Bearer token123" -``` - -## Command Reference - -### Navigation -``` -browse goto Navigate current tab -browse back Go back -browse forward Go forward -browse reload Reload page -browse url Print current URL -``` - -### Content extraction -``` -browse text Cleaned page text (no scripts/styles) -browse html [selector] innerHTML of element, or full page HTML -browse links All links as "text → href" -browse forms All forms + fields as JSON -browse accessibility Accessibility tree snapshot (ARIA) -``` - -### Snapshot (ref-based element selection) -``` -browse snapshot Full accessibility tree with @refs -browse snapshot -i Interactive elements only (buttons, links, inputs) -browse snapshot -c Compact (no empty structural elements) -browse snapshot -d Limit depth to N levels -browse snapshot -s Scope to CSS selector -``` - -After snapshot, use @refs as selectors in any command: -``` -browse click @e3 Click the element assigned ref @e3 -browse fill @e4 "value" Fill the input assigned ref @e4 -browse hover @e1 Hover the element assigned ref @e1 -browse html @e2 Get innerHTML of ref @e2 -browse css @e5 "color" Get computed CSS of ref @e5 -browse attrs @e6 Get attributes of ref @e6 -``` - -Refs are invalidated on navigation — run `snapshot` again after `goto`. - -### Interaction -``` -browse click Click element (CSS selector or @ref) -browse fill Fill input field -browse select Select dropdown value -browse hover Hover over element -browse type Type into focused element -browse press Press key (Enter, Tab, Escape, etc.) -browse scroll [selector] Scroll element into view, or page bottom -browse wait Wait for element to appear (max 10s) -browse viewport Set viewport size (e.g. 375x812) -``` - -### Inspection -``` -browse js Run JS, print result -browse eval Run JS file against page -browse css Get computed CSS property -browse attrs Get element attributes as JSON -browse console Dump captured console messages -browse console --clear Clear console buffer -browse network Dump captured network requests -browse network --clear Clear network buffer -browse cookies Dump all cookies as JSON -browse storage localStorage + sessionStorage as JSON -browse storage set Set localStorage value -browse perf Page load performance timings -``` - -### Visual -``` -browse screenshot [path] Screenshot (default: /tmp/browse-screenshot.png) -browse pdf [path] Save as PDF -browse responsive [prefix] Screenshots at mobile/tablet/desktop -``` - -### Compare -``` -browse diff Text diff between two pages -``` - -### Multi-step (chain) -``` -echo '[["goto","https://example.com"],["snapshot","-i"],["click","@e1"],["screenshot","/tmp/result.png"]]' | browse chain -``` - -### Tabs -``` -browse tabs List tabs (id, url, title) -browse tab Switch to tab -browse newtab [url] Open new tab -browse closetab [id] Close tab -``` - -### Server management -``` -browse status Server health, uptime, tab count -browse stop Shutdown server -browse restart Kill + restart server -``` - -## Speed Rules - -1. **Navigate once, query many times.** `goto` loads the page; then `text`, `js`, `css`, `screenshot` all run against the loaded page instantly. -2. **Use `snapshot -i` for interaction.** Get refs for all interactive elements, then click/fill by ref. No need to guess CSS selectors. -3. **Use `js` for precision.** `js "document.querySelector('.price').textContent"` is faster than parsing full page text. -4. **Use `links` to survey.** Faster than `text` when you just need navigation structure. -5. **Use `chain` for multi-step flows.** Avoids CLI overhead per step. -6. **Use `responsive` for layout checks.** One command = 3 viewport screenshots. - -## When to Use What - -| Task | Commands | -|------|----------| -| Read a page | `goto ` then `text` | -| Interact with elements | `snapshot -i` then `click @e3` | -| Check if element exists | `js "!!document.querySelector('.thing')"` | -| Extract specific data | `js "document.querySelector('.price').textContent"` | -| Visual check | `screenshot /tmp/x.png` then Read the image | -| Fill and submit form | `snapshot -i` → `fill @e4 "val"` → `click @e5` → `screenshot` | -| Check CSS | `css "selector" "property"` or `css @e3 "property"` | -| Inspect DOM | `html "selector"` or `attrs @e3` | -| Debug console errors | `console` | -| Check network requests | `network` | -| Check local dev | `goto http://127.0.0.1:3000` | -| Compare two pages | `diff ` | -| Mobile layout check | `responsive /tmp/prefix` | -| Multi-step flow | `echo '[...]' \| browse chain` | - -## Architecture - -- Persistent Chromium daemon on localhost (port 9400-9410) -- Bearer token auth per session -- State file: `/tmp/browse-server.json` -- Console log: `/tmp/browse-console.log` -- Network log: `/tmp/browse-network.log` -- Auto-shutdown after 30 min idle -- Chromium crash → server exits → auto-restarts on next command diff --git a/package.json b/package.json index d486f91f6..0a11ab099 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,14 @@ "license": "MIT", "type": "module", "bin": { - "browse": "./browse/dist/browse" + "browse": "./skills/browse/dist/browse" }, "scripts": { - "build": "bun build --compile browse/src/cli.ts --outfile browse/dist/browse", - "dev": "bun run browse/src/cli.ts", - "server": "bun run browse/src/server.ts", + "build": "bun build --compile skills/browse/src/cli.ts --outfile skills/browse/dist/browse", + "dev": "bun run skills/browse/src/cli.ts", + "server": "bun run skills/browse/src/server.ts", "test": "bun test", - "start": "bun run browse/src/server.ts" + "start": "bun run skills/browse/src/server.ts" }, "dependencies": { "playwright": "^1.58.2", diff --git a/setup b/setup deleted file mode 100755 index 73c6503e0..000000000 --- a/setup +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -# gstack setup — build browser binary + register all skills with Claude Code -set -e - -GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)" -SKILLS_DIR="$(dirname "$GSTACK_DIR")" -BROWSE_BIN="$GSTACK_DIR/browse/dist/browse" - -# 1. Build browse binary if needed -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 - ) -fi - -if [ ! -x "$BROWSE_BIN" ]; then - echo "gstack setup failed: browse binary missing at $BROWSE_BIN" >&2 - exit 1 -fi - -# 2. Only create skill symlinks if we're inside a .claude/skills directory -SKILLS_BASENAME="$(basename "$SKILLS_DIR")" -if [ "$SKILLS_BASENAME" = "skills" ]; then - 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 - - echo "gstack ready." - echo " browse: $BROWSE_BIN" - if [ ${#linked[@]} -gt 0 ]; then - echo " linked skills: ${linked[*]}" - fi -else - echo "gstack ready." - echo " browse: $BROWSE_BIN" - echo " (skipped skill symlinks — not inside .claude/skills/)" -fi diff --git a/browse/SKILL.md b/skills/browse/SKILL.md similarity index 88% rename from browse/SKILL.md rename to skills/browse/SKILL.md index b752aec68..34369625a 100644 --- a/browse/SKILL.md +++ b/skills/browse/SKILL.md @@ -20,35 +20,31 @@ Every subsequent call: ~100-200ms. Auto-shuts down after 30 min idle. ## SETUP (run this check BEFORE any browse command) -Before using any browse command, find the skill and check if the binary exists: +Before using any browse command, check if the binary exists: ```bash -# Check project-level first, then user-level -if test -x .claude/skills/gstack/browse/dist/browse; then - echo "READY_PROJECT" -elif test -x ~/.claude/skills/gstack/browse/dist/browse; then - echo "READY_USER" +if test -x "${CLAUDE_SKILL_DIR}/dist/browse"; then + echo "READY" else - echo "NEEDS_SETUP" + echo "NEEDS_BUILD" fi ``` -Set `B` to whichever path is READY and use it for all commands. Prefer project-level if both exist. +Set `B` to the binary path: `B="${CLAUDE_SKILL_DIR}/dist/browse"` -If `NEEDS_SETUP`: +If `NEEDS_BUILD`: 1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait for their response. -2. If they approve, determine the skill directory (project-level `.claude/skills/gstack` or user-level `~/.claude/skills/gstack`) and run: +2. If they approve, run: ```bash -cd && ./setup +cd "${CLAUDE_SKILL_DIR}" && bun install && bun build --compile src/cli.ts --outfile dist/browse ``` 3. If `bun` is not installed, tell the user to install it: `curl -fsSL https://bun.sh/install | bash` -4. Verify the `.gitignore` in the skill directory contains `browse/dist/` and `node_modules/`. If either line is missing, add it. -Once setup is done, it never needs to run again (the compiled binary persists). +Once built, the compiled binary persists across sessions. ## IMPORTANT -- Use the compiled binary via Bash: `.claude/skills/gstack/browse/dist/browse` (project) or `~/.claude/skills/gstack/browse/dist/browse` (user). +- Use the compiled binary via Bash: `${CLAUDE_SKILL_DIR}/dist/browse` - NEVER use `mcp__claude-in-chrome__*` tools. They are slow and unreliable. - The browser persists between calls — cookies, tabs, and state carry over. - The server auto-starts on first command. No setup needed. @@ -56,7 +52,7 @@ Once setup is done, it never needs to run again (the compiled binary persists). ## Quick Reference ```bash -B=~/.claude/skills/gstack/browse/dist/browse +B="${CLAUDE_SKILL_DIR}/dist/browse" # Navigate to a page $B goto https://example.com diff --git a/browse/src/browser-manager.ts b/skills/browse/src/browser-manager.ts similarity index 100% rename from browse/src/browser-manager.ts rename to skills/browse/src/browser-manager.ts diff --git a/browse/src/buffers.ts b/skills/browse/src/buffers.ts similarity index 100% rename from browse/src/buffers.ts rename to skills/browse/src/buffers.ts diff --git a/browse/src/cli.ts b/skills/browse/src/cli.ts similarity index 100% rename from browse/src/cli.ts rename to skills/browse/src/cli.ts diff --git a/browse/src/meta-commands.ts b/skills/browse/src/meta-commands.ts similarity index 100% rename from browse/src/meta-commands.ts rename to skills/browse/src/meta-commands.ts diff --git a/browse/src/read-commands.ts b/skills/browse/src/read-commands.ts similarity index 100% rename from browse/src/read-commands.ts rename to skills/browse/src/read-commands.ts diff --git a/browse/src/server.ts b/skills/browse/src/server.ts similarity index 100% rename from browse/src/server.ts rename to skills/browse/src/server.ts diff --git a/browse/src/snapshot.ts b/skills/browse/src/snapshot.ts similarity index 100% rename from browse/src/snapshot.ts rename to skills/browse/src/snapshot.ts diff --git a/browse/src/write-commands.ts b/skills/browse/src/write-commands.ts similarity index 100% rename from browse/src/write-commands.ts rename to skills/browse/src/write-commands.ts diff --git a/browse/test/commands.test.ts b/skills/browse/test/commands.test.ts similarity index 100% rename from browse/test/commands.test.ts rename to skills/browse/test/commands.test.ts diff --git a/browse/test/fixtures/basic.html b/skills/browse/test/fixtures/basic.html similarity index 100% rename from browse/test/fixtures/basic.html rename to skills/browse/test/fixtures/basic.html diff --git a/browse/test/fixtures/forms.html b/skills/browse/test/fixtures/forms.html similarity index 100% rename from browse/test/fixtures/forms.html rename to skills/browse/test/fixtures/forms.html diff --git a/browse/test/fixtures/responsive.html b/skills/browse/test/fixtures/responsive.html similarity index 100% rename from browse/test/fixtures/responsive.html rename to skills/browse/test/fixtures/responsive.html diff --git a/browse/test/fixtures/snapshot.html b/skills/browse/test/fixtures/snapshot.html similarity index 100% rename from browse/test/fixtures/snapshot.html rename to skills/browse/test/fixtures/snapshot.html diff --git a/browse/test/fixtures/spa.html b/skills/browse/test/fixtures/spa.html similarity index 100% rename from browse/test/fixtures/spa.html rename to skills/browse/test/fixtures/spa.html diff --git a/browse/test/snapshot.test.ts b/skills/browse/test/snapshot.test.ts similarity index 100% rename from browse/test/snapshot.test.ts rename to skills/browse/test/snapshot.test.ts diff --git a/browse/test/test-server.ts b/skills/browse/test/test-server.ts similarity index 100% rename from browse/test/test-server.ts rename to skills/browse/test/test-server.ts diff --git a/plan-ceo-review/SKILL.md b/skills/plan-ceo-review/SKILL.md similarity index 100% rename from plan-ceo-review/SKILL.md rename to skills/plan-ceo-review/SKILL.md diff --git a/plan-eng-review/SKILL.md b/skills/plan-eng-review/SKILL.md similarity index 100% rename from plan-eng-review/SKILL.md rename to skills/plan-eng-review/SKILL.md diff --git a/retro/SKILL.md b/skills/retro/SKILL.md similarity index 100% rename from retro/SKILL.md rename to skills/retro/SKILL.md diff --git a/review/SKILL.md b/skills/review/SKILL.md similarity index 98% rename from review/SKILL.md rename to skills/review/SKILL.md index ea6f7b75e..f09f47a60 100644 --- a/review/SKILL.md +++ b/skills/review/SKILL.md @@ -30,7 +30,7 @@ You are running the `/review` workflow. Analyze the current branch's diff agains ## Step 2: Read the checklist -Read `.claude/skills/review/checklist.md`. +Read `${CLAUDE_SKILL_DIR}/checklist.md`. **If the file cannot be read, STOP and report the error.** Do not proceed without the checklist. diff --git a/review/checklist.md b/skills/review/checklist.md similarity index 100% rename from review/checklist.md rename to skills/review/checklist.md diff --git a/ship/SKILL.md b/skills/ship/SKILL.md similarity index 99% rename from ship/SKILL.md rename to skills/ship/SKILL.md index bbf72c817..d6a54f6aa 100644 --- a/ship/SKILL.md +++ b/skills/ship/SKILL.md @@ -145,7 +145,7 @@ If multiple suites need to run, run them sequentially (each needs a test lane). Review the diff for structural issues that tests don't catch. -1. Read `.claude/skills/review/checklist.md`. If the file cannot be read, **STOP** and report the error. +1. Read `${CLAUDE_SKILL_DIR}/../review/checklist.md`. If the file cannot be read, **STOP** and report the error. 2. Run `git diff origin/main` to get the full diff (scoped to feature changes against the freshly-fetched remote main).