feat: add preview tool — navigate + screenshot in one call with optional POST

New MCP tool `preview` combines goto + screenshot with viewport control.
Optionally POSTs result to any HTTP endpoint (e.g. HUD/visor) via previewUrl.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 12:44:52 +02:00
parent 7171fbfc76
commit a80224df36
16 changed files with 332 additions and 272 deletions
+60 -229
View File
@@ -1,249 +1,80 @@
# Plan: Playwright Debugging Features
# Plan: Preview Tool — Single-Call Screenshot-to-Visor Pipeline
## Phase 1: Core Debugging (Console & Errors)
## Context
### Description
Implement fundamental debugging capabilities: console message capture and uncaught exception handling. These form the foundation for debugging client-side issues.
Currently, previewing an HTML mockup or live URL on the MARAUDER VISOR requires 4 sequential tool calls: `launch``goto``screenshot``bash curl POST /image`. This is slow, verbose, and prone to Claude pausing between steps to narrate.
### Steps
**Goal:** Add a `preview` tool to the browse MCP that does the entire pipeline in one call. Optionally pushes to the visor automatically.
#### Step 1.1: Add Console Command
- **Objective**: Capture and retrieve console messages from browser
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Status**: COMPLETE
- **Implementation**:
- Add `ConsoleCommand` type with `level` filter and `clear` option
- Store messages via `page.on('console')` listener
- Return messages with level, text, timestamp, location
## Design
#### Step 1.2: Add Page Errors Command
- **Objective**: Capture uncaught exceptions and unhandled promise rejections
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: Step 1.1
- **Implementation**:
- Add `ErrorsCommand` type with `clear` option
- Listen to `page.on('pageerror')` for uncaught exceptions
- Store error message, stack trace, timestamp
- Add `browser://errors` MCP resource
### New Tool: `preview`
## Phase 2: Network Monitoring
browse is a standalone npm package — it must NOT know about the visor. The `preview` tool is a convenience wrapper for "goto + screenshot with viewport control" in a single call.
### Description
Implement network request/response capture for debugging API calls, identifying failed requests, and inspecting payloads.
```typescript
server.tool('preview', 'Navigate to URL and screenshot in one call with custom viewport', {
url: z.string(), // URL or file:///path
width: z.number().optional().default(1280), // Viewport width
height: z.number().optional().default(800), // Viewport height
fullPage: z.boolean().optional().default(false), // Full page capture
output: z.string().optional().default('/tmp/preview.png'), // Screenshot path
});
```
### Steps
**Returns:**
```json
{
"ok": true,
"path": "/tmp/preview.png",
"url": "https://kwit.fit",
"title": "kwit*fit"
}
```
#### Step 2.1: Add Network Logging
- **Objective**: Capture all network requests and responses
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `NetworkCommand` type with `filter` and `clear` options
- Listen to `page.on('request')` and `page.on('response')`
- Store: url, method, status, resourceType, timing, headers
- Add optional body capture for XHR/fetch (with size limit)
- Add `browser://network` MCP resource
### Behavior
#### Step 2.2: Add Failed Requests Filter
- **Objective**: Quick access to failed/error requests
- **Files**: `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: Step 2.1
- **Implementation**:
- Add `failed` filter option to NetworkCommand
- Include requests with status >= 400 or network errors
- Add `browser://network/failed` MCP resource
1. If browser not launched → launch headless with given viewport dimensions
2. If browser already running with different viewport → resize to requested dimensions
3. Navigate to URL (supports `https://`, `http://`, `file:///`)
4. Wait for `networkidle` (with 5s timeout for SPAs)
5. Take screenshot → save to `output` path
6. Return result with path, url, and page title
#### Step 2.3: Add Request Interception
- **Objective**: Block or mock specific requests
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: Step 2.1
- **Implementation**:
- Add `InterceptCommand` with `action: 'block' | 'mock' | 'clear'`
- Support URL patterns (glob or regex)
- For mock: allow custom response body/status
- Use `page.route()` for interception
### Visor Integration (marauder-plugin side, NOT in browse)
## Phase 3: Performance & Metrics
The visor push lives in the marauder-plugin preview skill as a simple bash curl after the browse tool returns. This keeps browse generic and visor-agnostic.
### Description
Add performance timing and metrics collection for identifying bottlenecks and measuring page load characteristics.
## Files to Modify
### Steps
| File | Change |
|------|--------|
| `src/types.ts` | Add `PreviewCommand` interface + add to `BrowserCommand` union |
| `src/browser.ts` | Add `preview()` method + `pushToVisor()` helper + case in `executeCommand()` |
| `src/mcp.ts` | Register `preview` tool with zod schema |
| `src/index.ts` | No change needed (types auto-exported) |
#### Step 3.1: Add Performance Metrics
- **Objective**: Return page performance timing data
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `MetricsCommand` type
- Collect via `performance.timing` and `performance.getEntriesByType()`
- Return: domContentLoaded, load, firstPaint, firstContentfulPaint
- Include DOM stats: nodeCount, scriptCount, styleCount
## Files Unchanged
#### Step 3.2: Add Resource Timing
- **Objective**: Get timing breakdown for individual resources
- **Files**: `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: Step 3.1
- **Implementation**:
- Add `resources` option to MetricsCommand
- Use `performance.getEntriesByType('resource')`
- Return: name, duration, transferSize, initiatorType
- `src/cli.ts` — CLI doesn't need preview (it's an MCP-first tool)
- `src/image.ts` — No image processing needed
- `src/safari.ts`, `src/firefox.ts` — Unrelated
## Phase 4: Accessibility
## Risks
### Description
Implement accessibility tree inspection for debugging screen reader and a11y issues.
| Risk | Mitigation |
|------|------------|
| Browser already launched with wrong viewport | Resize viewport before navigating |
| `file:///` URLs blocked by Playwright | WebKit allows file:// by default |
| Visor not running | Silent fail, return `visor: false` in response |
| Node `fetch` not available | Node 18+ has native fetch; browse requires Node 18+ |
### Steps
## Verification
#### Step 4.1: Add Accessibility Snapshot
- **Objective**: Dump accessibility tree for page or element
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `A11yCommand` with optional `selector` for subtree
- Use `page.accessibility.snapshot()`
- Return tree with role, name, value, description
- Add `browser://a11y` MCP resource
## Phase 5: Dialog Handling
### Description
Implement automatic handling of browser dialogs (alert, confirm, prompt) to prevent blocking during automation.
### Steps
#### Step 5.1: Add Dialog Command
- **Objective**: Configure how dialogs are handled
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `DialogCommand` with `action: 'accept' | 'dismiss' | 'status'`
- Option to set default behavior for future dialogs
- Option to provide text response for prompts
- Store dialog history (type, message, response)
- Listen to `page.on('dialog')`
## Phase 6: Storage & Cookies
### Description
Add commands for inspecting and manipulating browser storage, complementing the existing session save/restore.
### Steps
#### Step 6.1: Add Cookies Command
- **Objective**: Get, set, and clear cookies
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `CookiesCommand` with `action: 'get' | 'set' | 'delete' | 'clear'`
- Use `context.cookies()` and `context.addCookies()`
- Support filtering by name/domain
- Add `browser://cookies` MCP resource
#### Step 6.2: Add Storage Command
- **Objective**: Inspect/modify localStorage and sessionStorage
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `StorageCommand` with `type: 'local' | 'session'`
- Actions: get, set, delete, clear
- Implement via `page.evaluate()`
- Add `browser://storage/local` and `browser://storage/session` resources
## Phase 7: Advanced Interactions
### Description
Add additional interaction commands for comprehensive testing scenarios.
### Steps
#### Step 7.1: Add Hover Command
- **Objective**: Trigger hover state on elements
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `HoverCommand` with `selector`
- Use `page.hover(selector)`
#### Step 7.2: Add Select Command
- **Objective**: Select options in dropdown elements
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `SelectCommand` with `selector` and `value` (or values array)
- Use `page.selectOption()`
#### Step 7.3: Add Keys Command
- **Objective**: Send keyboard shortcuts and special keys
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `KeysCommand` with `keys` string (e.g., "Control+a", "Escape")
- Use `page.keyboard.press()`
#### Step 7.4: Add Upload Command
- **Objective**: Set files on file input elements
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `UploadCommand` with `selector` and `files` array
- Use `page.setInputFiles()`
#### Step 7.5: Add Scroll Command
- **Objective**: Scroll page or element into view
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `ScrollCommand` with optional `selector`, `x`, `y`
- If selector: use `element.scrollIntoView()`
- Otherwise: use `window.scrollTo()`
## Phase 8: Viewport & Emulation
### Description
Add device emulation and viewport manipulation for responsive testing.
### Steps
#### Step 8.1: Add Viewport Command
- **Objective**: Resize browser viewport dynamically
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: None
- **Implementation**:
- Add `ViewportCommand` with `width` and `height`
- Use `page.setViewportSize()`
#### Step 8.2: Add Emulate Command
- **Objective**: Emulate specific devices
- **Files**: `src/types.ts`, `src/browser.ts`, `src/mcp.ts`
- **Dependencies**: Step 8.1
- **Implementation**:
- Add `EmulateCommand` with `device` name
- Use Playwright's device descriptors
- Apply viewport, userAgent, deviceScaleFactor, touch support
## Phase 9: Documentation
### Description
Update all documentation with new commands and examples.
### Steps
#### Step 9.1: Update README
- **Objective**: Document all commands with examples
- **Files**: `README.md`
- **Dependencies**: Phases 1-8
- **Implementation**:
- Add command reference grouped by category
- Include curl/MCP examples
- Document response formats
#### Step 9.2: Update CLAUDE.md
- **Objective**: Update developer documentation
- **Files**: `CLAUDE.md`
- **Dependencies**: Step 9.1
- **Implementation**:
- Update architecture notes
- Document new command types
1. `npm run build` — compiles
2. `npm run check` — passes lint/format
3. Manual test: call `preview` tool from Claude Code with a URL
4. Manual test: call `preview` with `file:///tmp/mockup.html`
5. Verify visor displays the screenshot
6. `npm run test` if tests exist
7. Bump version, publish to npm
+36
View File
@@ -2,6 +2,42 @@
Research notes on making Playwright WebKit less detectable by anti-bot systems. Compiled April 2026.
---
## NOTICE — Scope & Intent
**This is a web automation research document covering browser fingerprinting and bot detection evasion techniques.** It is compiled as technical reference for the `@saiden/browse` npm package -- a published, open-source headless browser tool for Claude Code.
### Context
Bot detection evasion is standard practice across several legitimate industries:
- **QA and test automation** -- Automated tests that trigger bot detection produce false failures, making stealth patches essential for reliable CI/CD pipelines
- **Price monitoring and comparison** -- E-commerce price tracking services (Honey, Camelcamelcamel, Google Shopping) routinely work around anti-bot measures
- **Academic web research** -- Researchers scraping public data for studies in computational social science, NLP corpus building, and web accessibility auditing
- **Accessibility testing** -- Automated accessibility scanners must behave like real browsers to accurately assess how sites serve content to assistive technologies
- **Search engine indexing** -- Crawlers from Google, Bing, and others implement similar techniques to access content behind bot detection layers
The Playwright, Puppeteer, and Selenium communities have maintained stealth plugins as standard tooling for years. This document compiles publicly known techniques specific to WebKit, which is less covered than Chromium in existing literature.
### What This Covers
- Browser fingerprinting vectors and their detection severity
- JavaScript-level patches for `addInitScript()` (Playwright's official API)
- Context configuration for realistic browser profiles
- Limitations of JS-level evasion (TLS fingerprinting, IP reputation, behavioral analysis)
- Anti-bot provider comparison for automated testing planning
### What This Is NOT
- Not a guide for bypassing paywalls or accessing content you are not authorised to view
- Not a credential stuffing or account takeover toolkit
- Not intended for scraping personal data in violation of privacy regulations (GDPR, CCPA)
All techniques referenced are publicly documented in the Playwright ecosystem, security research literature, and the web automation community. Sources are cited at the end of this document.
---
## Current State
Browse uses **Playwright WebKit** with a bare context — no stealth patches. This is trivially detected by every major anti-bot system (Cloudflare, DataDome, PerimeterX/HUMAN, Akamai).
+28 -34
View File
@@ -1,39 +1,33 @@
# TODO: Playwright Debugging Features
# TODO: Preview Tool
## Phase 1: Core Debugging (Console & Errors)
- [x] Step 1.1: Add Console Command
- [x] Step 1.2: Add Page Errors Command
## Phase 1: Add `preview` tool
- [ ] Add `PreviewCommand` interface to `src/types.ts`
- [ ] Add to `BrowserCommand` discriminated union
- [ ] Add `pushToVisor()` helper method to `ClaudeBrowser` in `src/browser.ts`
- [ ] Add `preview()` method to `ClaudeBrowser`
- [ ] Add `case 'preview'` in `executeCommand()` switch
- [ ] Register `preview` MCP tool in `src/mcp.ts` with zod schema
- [ ] `npm run build` — compiles clean
- [ ] `npm run check` — lint/format pass
## Phase 2: Network Monitoring
- [x] Step 2.1: Add Network Logging
- [x] Step 2.2: Add Failed Requests Filter
- [x] Step 2.3: Add Request Interception
## Phase 2: Test & Publish
- [ ] Test with URL: `preview({ url: "https://kwit.fit", title: "TEST" })`
- [ ] Test with file: `preview({ url: "file:///tmp/test.html" })`
- [ ] Test visor push works
- [ ] Test visor-down graceful fallback (`visor: false` in response)
- [ ] Test viewport resize when browser already running
- [ ] Bump version, publish to npm
- [ ] Update marauder-plugin `.mcp.json` if needed
## Phase 3: Performance & Metrics
- [x] Step 3.1: Add Performance Metrics
- [x] Step 3.2: Add Resource Timing
## Phase 3: Skill cleanup
- [ ] Delete `marauder-plugin/skills/preview/preview.py`
- [ ] Rewrite `marauder-plugin/skills/preview/SKILL.md` as simple one-liner reference
## Phase 4: Accessibility
- [x] Step 4.1: Add Accessibility Snapshot
### ETA
## Phase 5: Dialog Handling
- [x] Step 5.1: Add Dialog Command
## Phase 6: Storage & Cookies
- [x] Step 6.1: Add Cookies Command
- [x] Step 6.2: Add Storage Command
## Phase 7: Advanced Interactions
- [x] Step 7.1: Add Hover Command
- [x] Step 7.2: Add Select Command
- [x] Step 7.3: Add Keys Command
- [x] Step 7.4: Add Upload Command
- [x] Step 7.5: Add Scroll Command
## Phase 8: Viewport & Emulation
- [x] Step 8.1: Add Viewport Command
- [x] Step 8.2: Add Emulate Command
## Phase 9: Documentation
- [x] Step 9.1: Update README
- [x] Step 9.2: Update CLAUDE.md
| Phase | Naive | Coop | Sessions | Notes |
|-------|-------|------|----------|-------|
| 1. Add tool | 2h | ~30m | 1 | Mechanical — follow existing pattern exactly |
| 2. Test & publish | 1h | ~15m | 1 | Same session |
| 3. Skill cleanup | 30m | ~10m | 1 | Delete + rewrite |
| **Total** | **3.5h** | **~55m** | **1** | Single session, single commit |
+7
View File
@@ -118,6 +118,13 @@ export declare class ClaudeBrowser {
private handleDialogCommand;
private handleCookiesCommand;
private handleStorageCommand;
private handlePreviewCommand;
/**
* POST a screenshot to a preview endpoint.
* Payload: { source: "file:///path", title, caption }
* Silent failure — returns false if endpoint is unreachable.
*/
private postPreview;
private handleImportCommand;
executeCommand(cmd: BrowserCommand): Promise<CommandResponse>;
}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
+47
View File
@@ -769,6 +769,51 @@ export class ClaudeBrowser {
return { ok: false, error: 'Unknown storage action' };
}
}
async handlePreviewCommand(cmd) {
const page = this.ensurePage();
// Resize viewport if dimensions specified
if (cmd.width || cmd.height) {
const current = page.viewportSize();
const width = cmd.width || current?.width || 1280;
const height = cmd.height || current?.height || 800;
await page.setViewportSize({ width, height });
}
// Navigate
const nav = await this.goto(cmd.url);
// Screenshot
const outputPath = resolve(cmd.output || '/tmp/preview.png');
await page.screenshot({ path: outputPath, fullPage: cmd.fullPage || false });
// Optional: POST to preview endpoint
let posted = false;
if (cmd.previewUrl) {
posted = await this.postPreview(outputPath, cmd.previewUrl, cmd.title, cmd.caption);
}
return { ok: true, path: outputPath, url: nav.url, title: nav.title, posted };
}
/**
* POST a screenshot to a preview endpoint.
* Payload: { source: "file:///path", title, caption }
* Silent failure — returns false if endpoint is unreachable.
*/
async postPreview(imagePath, previewUrl, title, caption) {
try {
const payload = JSON.stringify({
source: `file://${resolve(imagePath)}`,
title: title || null,
caption: caption || null,
});
const res = await fetch(previewUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
signal: AbortSignal.timeout(3000),
});
return res.ok;
}
catch {
return false;
}
}
async handleImportCommand(cmd) {
const context = this.getContext();
if (!context)
@@ -1012,6 +1057,8 @@ export class ClaudeBrowser {
}
case 'import':
return this.handleImportCommand(cmd);
case 'preview':
return this.handlePreviewCommand(cmd);
default: {
const _exhaustive = cmd;
return { ok: false, error: `Unknown command: ${_exhaustive.cmd}` };
+1 -1
View File
File diff suppressed because one or more lines are too long
Vendored
+25
View File
@@ -168,6 +168,31 @@ server.tool('screenshot', 'Take a screenshot of the current page', {
const result = await browser.screenshot(path, fullPage);
return textResult(JSON.stringify({ ok: true, path: result.path }));
}));
// Preview — navigate + screenshot in one call, optional POST to preview endpoint
server.tool('preview', 'Navigate to a URL and take a screenshot in one call. Optionally POST the result to a preview endpoint.', {
url: z.string().describe('URL or file:///path to preview'),
width: z.number().optional().default(1280).describe('Viewport width'),
height: z.number().optional().default(800).describe('Viewport height'),
fullPage: z.boolean().optional().default(false),
output: z.string().optional().default('/tmp/preview.png').describe('Screenshot output path'),
previewUrl: z.string().optional().describe('HTTP endpoint to POST screenshot result to'),
title: z.string().optional().describe('Title sent with preview POST'),
caption: z.string().optional().describe('Caption sent with preview POST'),
}, withLogging('preview', async ({ url, width, height, fullPage, output, previewUrl, title, caption }) => {
await ensureLaunched();
const result = await browser.executeCommand({
cmd: 'preview',
url,
width,
height,
fullPage,
output,
previewUrl,
title,
caption,
});
return textResult(JSON.stringify(result));
}));
// Eval
server.tool('eval', 'Execute JavaScript in the browser context', { script: z.string() }, withLogging('eval', async ({ script }) => {
await ensureLaunched();
+1 -1
View File
File diff suppressed because one or more lines are too long
+13 -1
View File
@@ -259,7 +259,18 @@ export interface ImportCommand {
domain?: string;
profile?: string;
}
export type BrowserCommand = GotoCommand | ClickCommand | TypeCommand | QueryCommand | ScreenshotCommand | UrlCommand | HtmlCommand | BackCommand | ForwardCommand | ReloadCommand | WaitCommand | NewPageCommand | CloseCommand | EvalCommand | FaviconCommand | ConvertCommand | ResizeCommand | CropCommand | CompressCommand | ThumbnailCommand | ConsoleCommand | NetworkCommand | InterceptCommand | ErrorsCommand | MetricsCommand | A11yCommand | DialogCommand | CookiesCommand | StorageCommand | HoverCommand | SelectCommand | KeysCommand | UploadCommand | ScrollCommand | ViewportCommand | EmulateCommand | ImportCommand;
export type BrowserCommand = GotoCommand | ClickCommand | TypeCommand | QueryCommand | ScreenshotCommand | UrlCommand | HtmlCommand | BackCommand | ForwardCommand | ReloadCommand | WaitCommand | NewPageCommand | CloseCommand | EvalCommand | FaviconCommand | ConvertCommand | ResizeCommand | CropCommand | CompressCommand | ThumbnailCommand | ConsoleCommand | NetworkCommand | InterceptCommand | ErrorsCommand | MetricsCommand | A11yCommand | DialogCommand | CookiesCommand | StorageCommand | HoverCommand | SelectCommand | KeysCommand | UploadCommand | ScrollCommand | ViewportCommand | EmulateCommand | ImportCommand | PreviewCommand;
export interface PreviewCommand {
cmd: 'preview';
url: string;
width?: number;
height?: number;
fullPage?: boolean;
output?: string;
previewUrl?: string;
title?: string;
caption?: string;
}
export interface SuccessResponse {
ok: true;
url?: string;
@@ -269,6 +280,7 @@ export interface SuccessResponse {
count?: number;
elements?: ElementInfo[];
result?: unknown;
posted?: boolean;
files?: string[];
outputDir?: string;
width?: number;
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@saiden/browse",
"version": "0.3.3",
"version": "0.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@saiden/browse",
"version": "0.3.3",
"version": "0.4.0",
"license": "BUSL-1.1",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@saiden/browse",
"version": "0.3.3",
"version": "0.4.0",
"description": "Headless browser automation for Claude Code using Playwright WebKit",
"type": "module",
"main": "dist/index.js",
+59
View File
@@ -21,6 +21,7 @@ import type {
MetricsData,
NetworkEntry,
PageError,
PreviewCommand,
StorageCommand,
} from './types.js';
@@ -878,6 +879,62 @@ export class ClaudeBrowser {
}
}
private async handlePreviewCommand(cmd: PreviewCommand): Promise<CommandResponse> {
const page = this.ensurePage();
// Resize viewport if dimensions specified
if (cmd.width || cmd.height) {
const current = page.viewportSize();
const width = cmd.width || current?.width || 1280;
const height = cmd.height || current?.height || 800;
await page.setViewportSize({ width, height });
}
// Navigate
const nav = await this.goto(cmd.url);
// Screenshot
const outputPath = resolve(cmd.output || '/tmp/preview.png');
await page.screenshot({ path: outputPath, fullPage: cmd.fullPage || false });
// Optional: POST to preview endpoint
let posted = false;
if (cmd.previewUrl) {
posted = await this.postPreview(outputPath, cmd.previewUrl, cmd.title, cmd.caption);
}
return { ok: true, path: outputPath, url: nav.url, title: nav.title, posted };
}
/**
* POST a screenshot to a preview endpoint.
* Payload: { source: "file:///path", title, caption }
* Silent failure — returns false if endpoint is unreachable.
*/
private async postPreview(
imagePath: string,
previewUrl: string,
title?: string,
caption?: string
): Promise<boolean> {
try {
const payload = JSON.stringify({
source: `file://${resolve(imagePath)}`,
title: title || null,
caption: caption || null,
});
const res = await fetch(previewUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
signal: AbortSignal.timeout(3000),
});
return res.ok;
} catch {
return false;
}
}
private async handleImportCommand(cmd: ImportCommand): Promise<CommandResponse> {
const context = this.getContext();
if (!context) throw new Error('Browser not launched');
@@ -1139,6 +1196,8 @@ export class ClaudeBrowser {
}
case 'import':
return this.handleImportCommand(cmd);
case 'preview':
return this.handlePreviewCommand(cmd);
default: {
const _exhaustive: never = cmd;
return { ok: false, error: `Unknown command: ${(_exhaustive as { cmd: string }).cmd}` };
+34
View File
@@ -255,6 +255,40 @@ server.tool(
})
);
// Preview — navigate + screenshot in one call, optional POST to preview endpoint
server.tool(
'preview',
'Navigate to a URL and take a screenshot in one call. Optionally POST the result to a preview endpoint.',
{
url: z.string().describe('URL or file:///path to preview'),
width: z.number().optional().default(1280).describe('Viewport width'),
height: z.number().optional().default(800).describe('Viewport height'),
fullPage: z.boolean().optional().default(false),
output: z.string().optional().default('/tmp/preview.png').describe('Screenshot output path'),
previewUrl: z.string().optional().describe('HTTP endpoint to POST screenshot result to'),
title: z.string().optional().describe('Title sent with preview POST'),
caption: z.string().optional().describe('Caption sent with preview POST'),
},
withLogging(
'preview',
async ({ url, width, height, fullPage, output, previewUrl, title, caption }) => {
await ensureLaunched();
const result = await browser.executeCommand({
cmd: 'preview',
url,
width,
height,
fullPage,
output,
previewUrl,
title,
caption,
});
return textResult(JSON.stringify(result));
}
)
);
// Eval
server.tool(
'eval',
+16 -1
View File
@@ -347,7 +347,20 @@ export type BrowserCommand =
| ScrollCommand
| ViewportCommand
| EmulateCommand
| ImportCommand;
| ImportCommand
| PreviewCommand;
export interface PreviewCommand {
cmd: 'preview';
url: string;
width?: number;
height?: number;
fullPage?: boolean;
output?: string;
previewUrl?: string;
title?: string;
caption?: string;
}
// Response types
export interface SuccessResponse {
@@ -359,6 +372,8 @@ export interface SuccessResponse {
count?: number;
elements?: ElementInfo[];
result?: unknown;
// Preview fields
posted?: boolean;
// Image processing fields
files?: string[];
outputDir?: string;