diff --git a/browse/src/server.ts b/browse/src/server.ts index b7368096..7e12e8dc 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -949,6 +949,29 @@ async function start() { return handleCookiePickerRoute(url, req, browserManager, AUTH_TOKEN); } + // Welcome page — served when GStack Browser launches in headed mode + if (url.pathname === '/welcome') { + const welcomePath = (() => { + // Check project-local designs first, then global + const slug = process.env.GSTACK_SLUG || 'unknown'; + const projectWelcome = `${process.env.HOME}/.gstack/projects/${slug}/designs/welcome-page-20260331/finalized.html`; + try { if (require('fs').existsSync(projectWelcome)) return projectWelcome; } catch {} + // Fallback: built-in welcome page from gstack install + const skillRoot = process.env.GSTACK_SKILL_ROOT || `${process.env.HOME}/.claude/skills/gstack`; + const builtinWelcome = `${skillRoot}/browse/src/welcome.html`; + try { if (require('fs').existsSync(builtinWelcome)) return builtinWelcome; } catch {} + return null; + })(); + if (welcomePath) { + try { + const html = require('fs').readFileSync(welcomePath, 'utf-8'); + return new Response(html, { headers: { 'Content-Type': 'text/html; charset=utf-8' } }); + } catch {} + } + // No welcome page found — redirect to about:blank + return new Response('', { status: 302, headers: { 'Location': 'about:blank' } }); + } + // Health check — no auth required, does NOT reset idle timer if (url.pathname === '/health') { const healthy = await browserManager.isHealthy(); @@ -1494,6 +1517,17 @@ async function start() { browserManager.serverPort = port; + // Navigate to welcome page if in headed mode and still on about:blank + if (browserManager.getConnectionMode() === 'headed') { + try { + const currentUrl = browserManager.getCurrentUrl(); + if (currentUrl === 'about:blank' || currentUrl === '') { + const page = browserManager.getPage(); + page.goto(`http://127.0.0.1:${port}/welcome`, { timeout: 3000 }).catch(() => {}); + } + } catch {} + } + // Clean up stale state files (older than 7 days) try { const stateDir = path.join(config.stateDir, 'browse-states'); diff --git a/browse/src/welcome.html b/browse/src/welcome.html new file mode 100644 index 00000000..ac07f5d1 --- /dev/null +++ b/browse/src/welcome.html @@ -0,0 +1,230 @@ + + +
+ + +This browser is connected to your Claude Code session. The sidebar is your co-pilot: it can control this window, read pages, edit CSS, and pass everything back to your terminal.
+The sidebar chat is a Claude instance that controls this browser. Say "go to my app and check if login works" and watch it navigate, click, fill forms, and report back. It sees the same page you do.
+Click the Cleanup button in the sidebar. An AI reads the page, identifies overlays, paywalls, cookie banners, and clutter, then removes them. Articles become readable. Works on news sites, docs, anything.
+The screenshot button captures a cleaned, annotated screenshot and sends it to your Claude Code session as context. "What's wrong with this page?" now has a visual answer.
The sidebar can edit CSS and DOM on any page. "Make the header sticky" or "change the font to Inter." Changes happen live. The sidebar reports what it changed back to your Claude Code terminal.
+