diff --git a/skills/dev-browser/SKILL.md b/skills/dev-browser/SKILL.md index 5ac2826..78119e2 100644 --- a/skills/dev-browser/SKILL.md +++ b/skills/dev-browser/SKILL.md @@ -53,12 +53,20 @@ 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' 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,8 +87,9 @@ 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 +await page.setViewportSize({ width: 1280, height: 800 }); // Required for screenshots // Your automation code here await page.goto("https://example.com"); @@ -135,7 +144,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 +198,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 +257,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 +298,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;