From 5957d581f9b9a36bc9580c7f6707c25befd1c1f1 Mon Sep 17 00:00:00 2001 From: Sawyer Hood Date: Mon, 15 Dec 2025 20:20:36 -0800 Subject: [PATCH 1/2] Auto-connect to existing server instead of restarting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Make `connect()` default to `http://localhost:9222` so callers don't need to pass a URL - Change `start-server.ts` to no-op if server is already running instead of killing it - Keep CDP port cleanup for crash recovery (when Node dies but Chrome is still running) - Update SKILL.md documentation examples to use `connect()` without arguments 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- skills/dev-browser/SKILL.md | 12 ++++---- skills/dev-browser/scripts/start-server.ts | 34 +++++++++++++--------- skills/dev-browser/src/client.ts | 2 +- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/skills/dev-browser/SKILL.md b/skills/dev-browser/SKILL.md index 5ac2826..5d68151 100644 --- a/skills/dev-browser/SKILL.md +++ b/skills/dev-browser/SKILL.md @@ -58,7 +58,7 @@ Execute scripts inline using heredocs—no need to write files for one-off autom ```bash cd skills/dev-browser && bun x tsx <<'EOF' import { connect } from "@/client.js"; -const client = await connect("http://localhost:9222"); +const client = await connect(); const page = await client.page("main"); // Your automation code here await client.disconnect(); @@ -79,7 +79,7 @@ Use the `@/client.js` import path for all scripts. cd skills/dev-browser && bun x tsx <<'EOF' import { connect, waitForPageLoad } from "@/client.js"; -const client = await connect("http://localhost:9222"); +const client = await connect(); const page = await client.page("main"); // get or create a named page // Your automation code here @@ -135,7 +135,7 @@ Follow this pattern for complex tasks: ## Client API ```typescript -const client = await connect("http://localhost:9222"); +const client = await connect(); const page = await client.page("name"); // Get or create named page const pages = await client.list(); // List all page names await client.close("name"); // Close a page @@ -189,7 +189,7 @@ Use `getAISnapshot()` when you don't know the page layout and need to discover w cd skills/dev-browser && bun x tsx <<'EOF' import { connect, waitForPageLoad } from "@/client.js"; -const client = await connect("http://localhost:9222"); +const client = await connect(); const page = await client.page("main"); await page.goto("https://news.ycombinator.com"); @@ -248,7 +248,7 @@ Use `selectSnapshotRef()` to get a Playwright ElementHandle for any ref: cd skills/dev-browser && bun x tsx <<'EOF' import { connect, waitForPageLoad } from "@/client.js"; -const client = await connect("http://localhost:9222"); +const client = await connect(); const page = await client.page("main"); await page.goto("https://news.ycombinator.com"); @@ -289,7 +289,7 @@ If a script fails, the page state is preserved. You can: cd skills/dev-browser && bun x tsx <<'EOF' import { connect } from "@/client.js"; -const client = await connect("http://localhost:9222"); +const client = await connect(); const page = await client.page("main"); await page.screenshot({ path: "tmp/debug.png" }); diff --git a/skills/dev-browser/scripts/start-server.ts b/skills/dev-browser/scripts/start-server.ts index 6ff7190..e130a27 100644 --- a/skills/dev-browser/scripts/start-server.ts +++ b/skills/dev-browser/scripts/start-server.ts @@ -72,24 +72,30 @@ try { console.log("You may need to run: npx playwright install chromium"); } -// Kill any existing process on port 9222 (HTTP API) and 9223 (CDP) +// Check if server is already running console.log("Checking for existing servers..."); try { - // Find and kill processes on our ports - const ports = [9222, 9223]; - for (const port of ports) { - try { - const pid = execSync(`lsof -ti:${port}`, { encoding: "utf-8" }).trim(); - if (pid) { - console.log(`Killing existing process on port ${port} (PID: ${pid})`); - execSync(`kill -9 ${pid}`); - } - } catch { - // No process on this port, which is fine - } + const res = await fetch("http://localhost:9222", { + signal: AbortSignal.timeout(1000), + }); + if (res.ok) { + console.log("Server already running on port 9222"); + process.exit(0); + } +} catch { + // Server not running, continue to start +} + +// Clean up stale CDP port if HTTP server isn't running (crash recovery) +// This handles the case where Node crashed but Chrome is still running on 9223 +try { + const pid = execSync("lsof -ti:9223", { encoding: "utf-8" }).trim(); + if (pid) { + console.log(`Cleaning up stale Chrome process on CDP port 9223 (PID: ${pid})`); + execSync(`kill -9 ${pid}`); } } catch { - // lsof not available or no processes found + // No process on CDP port, which is expected } console.log("Starting dev browser server..."); diff --git a/skills/dev-browser/src/client.ts b/skills/dev-browser/src/client.ts index 1bbfcca..9c95408 100644 --- a/skills/dev-browser/src/client.ts +++ b/skills/dev-browser/src/client.ts @@ -224,7 +224,7 @@ export interface DevBrowserClient { selectSnapshotRef: (name: string, ref: string) => Promise; } -export async function connect(serverUrl: string): Promise { +export async function connect(serverUrl = "http://localhost:9222"): Promise { let browser: Browser | null = null; let wsEndpoint: string | null = null; let connectingPromise: Promise | null = null; From 07cda217a96e8862bf1add3e4cbea157f797564c Mon Sep 17 00:00:00 2001 From: Sawyer Hood Date: Tue, 16 Dec 2025 15:19:59 -0800 Subject: [PATCH 2/2] Add clarity for directory requirement and viewport setup in SKILL.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add prominent callout warning that scripts must run from skills/dev-browser/ - Explain why: @/ path alias is configured in that directory's config files - Add setViewportSize to basic template to prevent screenshot errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- skills/dev-browser/SKILL.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/skills/dev-browser/SKILL.md b/skills/dev-browser/SKILL.md index 5d68151..78119e2 100644 --- a/skills/dev-browser/SKILL.md +++ b/skills/dev-browser/SKILL.md @@ -53,7 +53,15 @@ The server starts a Chromium browser with a REST API for page management (defaul ## Writing Scripts -Execute scripts inline using heredocs—no need to write files for one-off automation: +Execute scripts inline using heredocs—no need to write files for one-off automation. + +> **CRITICAL: Always run scripts from `skills/dev-browser/`** +> +> Scripts must be executed from the `skills/dev-browser/` directory. The `@/` import alias (e.g., `@/client.js`) is configured in this directory's `tsconfig.json` and `package.json`. Running from any other directory will fail with: +> +> ``` +> ERR_MODULE_NOT_FOUND: Cannot find package '@/client.js' +> ``` ```bash cd skills/dev-browser && bun x tsx <<'EOF' @@ -81,6 +89,7 @@ import { connect, waitForPageLoad } from "@/client.js"; const client = await connect(); const page = await client.page("main"); // get or create a named page +await page.setViewportSize({ width: 1280, height: 800 }); // Required for screenshots // Your automation code here await page.goto("https://example.com");