#!/usr/bin/env node import { resolve } from 'node:path'; import { parseArgs } from 'node:util'; import { ClaudeBrowser } from './browser.js'; import { startServer } from './server.js'; import type { ElementInfo } from './types.js'; const { values, positionals } = parseArgs({ allowPositionals: true, options: { output: { type: 'string', short: 'o', default: 'screenshot.png' }, width: { type: 'string', short: 'w', default: '1280' }, height: { type: 'string', short: 'h', default: '800' }, fullpage: { type: 'boolean', short: 'f', default: false }, wait: { type: 'string', default: '2000' }, headed: { type: 'boolean', default: false }, interactive: { type: 'boolean', short: 'i', default: false }, query: { type: 'string', short: 'q' }, json: { type: 'boolean', short: 'j', default: false }, click: { type: 'string', short: 'c', multiple: true }, type: { type: 'string', short: 't', multiple: true }, help: { type: 'boolean', default: false }, version: { type: 'boolean', short: 'v', default: false }, }, }); const HELP = ` Usage: claude-browse [options] Options: -o, --output Output screenshot path (default: screenshot.png) -w, --width Viewport width (default: 1280) -h, --height Viewport height (default: 800) -f, --fullpage Capture full page scroll --wait Wait time after load (default: 2000) --headed Show browser window -i, --interactive Keep browser open for manual interaction -q, --query Query elements by CSS selector and show attributes -j, --json Output query results as JSON -c, --click Click on element (can be repeated for multiple clicks) -t, --type = Type text into input (can be repeated) -v, --version Show version --help Show this help Examples: claude-browse https://example.com claude-browse -o page.png -w 1920 -h 1080 https://example.com claude-browse -i --headed https://example.com claude-browse -q "a[href]" https://example.com claude-browse -q "img" -j https://example.com claude-browse -c "button.submit" https://example.com claude-browse -t "input[name=q]=hello" -c "button[type=submit]" https://google.com claude-browse -c ".cookie-accept" -c "a.nav-link" -q "h1" https://example.com Server mode (default): claude-browse # Start server on port 13373 claude-browse --headed # Start with visible browser # Send commands via curl: curl -X POST http://localhost:13373 -d '{"cmd":"goto","url":"https://example.com"}' curl -X POST http://localhost:13373 -d '{"cmd":"click","selector":"button"}' curl -X POST http://localhost:13373 -d '{"cmd":"type","selector":"input","text":"hello"}' curl -X POST http://localhost:13373 -d '{"cmd":"query","selector":"a[href]"}' curl -X POST http://localhost:13373 -d '{"cmd":"screenshot","path":"shot.png"}' curl -X POST http://localhost:13373 -d '{"cmd":"url"}' curl -X POST http://localhost:13373 -d '{"cmd":"html"}' curl -X POST http://localhost:13373 -d '{"cmd":"close"}' `; function getViewportConfig() { return { headless: !values.headed, width: Number.parseInt(values.width as string), height: Number.parseInt(values.height as string), }; } async function runServerMode(): Promise { const port = 13373; const server = await startServer({ port, ...getViewportConfig() }); process.on('SIGINT', async () => { console.log('\nShutting down...'); await server.stop(); process.exit(0); }); await new Promise(() => {}); } async function processTypeActions(browser: ClaudeBrowser): Promise { const typeActions = values.type as string[] | undefined; if (!typeActions?.length) return; for (const typeAction of typeActions) { const eqIndex = typeAction.indexOf('='); if (eqIndex === -1) { console.error(`Invalid --type format: "${typeAction}" (expected selector=text)`); continue; } const selector = typeAction.slice(0, eqIndex); const text = typeAction.slice(eqIndex + 1); console.log(`Typing "${text}" into: ${selector}`); await browser.type(selector, text); } } async function processClickActions(browser: ClaudeBrowser): Promise { const clickActions = values.click as string[] | undefined; if (!clickActions?.length) return; for (const selector of clickActions) { console.log(`Clicking: ${selector}`); await browser.click(selector); await browser.wait(500); } const { url: currentUrl } = await browser.getUrl(); console.log(`Current URL: ${currentUrl}`); } function printElement(el: ElementInfo, index: number): void { console.log(`[${index + 1}] <${el.tag}>`); for (const [name, value] of Object.entries(el.attributes)) { console.log(` ${name}="${value}"`); } if (el.text) { const truncated = el.text.length > 100 ? `${el.text.slice(0, 100)}...` : el.text; console.log(` text: "${truncated}"`); } console.log(); } async function runQueryMode(browser: ClaudeBrowser): Promise { const elements = await browser.query(values.query as string); if (values.json) { console.log(JSON.stringify(elements, null, 2)); } else { console.log(`Found ${elements.length} element(s) matching "${values.query}":\n`); elements.forEach(printElement); } await browser.close(); process.exit(0); } async function runInteractiveMode(browser: ClaudeBrowser): Promise { console.log('Interactive mode - browser will stay open.'); console.log('Press Ctrl+C to exit.'); process.on('SIGINT', async () => { console.log('\nClosing browser...'); await browser.close(); process.exit(0); }); await new Promise(() => {}); } async function runScreenshotMode(browser: ClaudeBrowser): Promise { const outputPath = resolve(values.output as string); console.log(`Saving screenshot to: ${outputPath}`); await browser.screenshot(outputPath, values.fullpage); await browser.close(); console.log('Done!'); } async function runBrowserMode(): Promise { const url = positionals[0]; const browser = new ClaudeBrowser(getViewportConfig()); await browser.launch(); console.log(`Navigating to: ${url}`); await browser.goto(url); await browser.wait(Number.parseInt(values.wait as string)); await processTypeActions(browser); await processClickActions(browser); if (values.query) { await runQueryMode(browser); return; } if (values.interactive) { await runInteractiveMode(browser); } else { await runScreenshotMode(browser); } } async function main(): Promise { if (values.version) { console.log('claude-browse 0.1.0'); process.exit(0); } if (values.help) { console.log(HELP); process.exit(0); } if (positionals.length === 0) { await runServerMode(); return; } await runBrowserMode(); } main().catch((err) => { console.error('Error:', err.message); process.exit(1); });