Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions skills/dev-browser/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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" });
Expand Down
34 changes: 20 additions & 14 deletions skills/dev-browser/scripts/start-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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...");
Expand Down
2 changes: 1 addition & 1 deletion skills/dev-browser/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export interface DevBrowserClient {
selectSnapshotRef: (name: string, ref: string) => Promise<ElementHandle | null>;
}

export async function connect(serverUrl: string): Promise<DevBrowserClient> {
export async function connect(serverUrl = "http://localhost:9222"): Promise<DevBrowserClient> {
let browser: Browser | null = null;
let wsEndpoint: string | null = null;
let connectingPromise: Promise<Browser> | null = null;
Expand Down