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 @@ + + + + + +GStack Browser + + + + + + + + +
+
+
+
+ GStack Browser +
+

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.

+
+ +
+
+
Talk to the sidebar
+

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.

+
+
+
Clean up any page
+

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.

+
+
+
Smart screenshots
+

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.

+
+
+
Modify any page
+

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.

+
+
+ +
+
Try it now
+
+
Open the sidebar and type: "Go to news.ycombinator.com, open the top story, clean up the article, and summarize the key points back to my terminal"
+
On any article page, click Cleanup to strip away the noise
+
Click Screenshot to capture the page and send it to your Claude Code session
+
Ask the sidebar: "Inspect the CSS on this page and send the color palette to my terminal"
+
+
+ + +
+ + + +