Files
stef-openclaw-skills/skills/web-automation/scripts/browse.ts
Luke 658562ae35 Add web-automation skill
- Browse and scrape web pages using Playwright with Camoufox anti-detection browser
- Supports automated web workflows, authenticated sessions, and bot protection bypass
- Includes scripts for browse, scrape, auth, and local app scanning
- Updated README with skill documentation and system library requirements
2026-02-11 18:46:59 +00:00

196 lines
5.3 KiB
TypeScript

#!/usr/bin/env npx tsx
/**
* Browser launcher using Camoufox with persistent profile
*
* Usage:
* npx tsx browse.ts --url "https://example.com"
* npx tsx browse.ts --url "https://example.com" --screenshot --output page.png
* npx tsx browse.ts --url "https://example.com" --headless false --wait 5000
*/
import { Camoufox } from 'camoufox-js';
import { homedir } from 'os';
import { join } from 'path';
import { existsSync, mkdirSync } from 'fs';
import parseArgs from 'minimist';
import type { Page, BrowserContext } from 'playwright-core';
// Types
interface BrowseOptions {
url: string;
headless?: boolean;
screenshot?: boolean;
output?: string;
wait?: number;
timeout?: number;
interactive?: boolean;
}
interface BrowseResult {
title: string;
url: string;
screenshotPath?: string;
}
// Get profile directory
const getProfilePath = (): string => {
const customPath = process.env.CAMOUFOX_PROFILE_PATH;
if (customPath) return customPath;
const profileDir = join(homedir(), '.camoufox-profile');
if (!existsSync(profileDir)) {
mkdirSync(profileDir, { recursive: true });
}
return profileDir;
};
// Launch browser with persistent profile
export async function launchBrowser(options: {
headless?: boolean;
}): Promise<BrowserContext> {
const profilePath = getProfilePath();
const headless =
options.headless ??
(process.env.CAMOUFOX_HEADLESS ? process.env.CAMOUFOX_HEADLESS === 'true' : true);
console.log(`Using profile: ${profilePath}`);
console.log(`Headless mode: ${headless}`);
const browser = await Camoufox({
user_data_dir: profilePath,
headless,
});
return browser;
}
// Browse to URL and optionally take screenshot
export async function browse(options: BrowseOptions): Promise<BrowseResult> {
const browser = await launchBrowser({ headless: options.headless });
const page = await browser.newPage();
try {
// Navigate to URL
console.log(`Navigating to: ${options.url}`);
await page.goto(options.url, {
timeout: options.timeout ?? 60000,
waitUntil: 'domcontentloaded',
});
// Wait if specified
if (options.wait) {
console.log(`Waiting ${options.wait}ms...`);
await page.waitForTimeout(options.wait);
}
const result: BrowseResult = {
title: await page.title(),
url: page.url(),
};
console.log(`Page title: ${result.title}`);
console.log(`Final URL: ${result.url}`);
// Take screenshot if requested
if (options.screenshot) {
const outputPath = options.output ?? 'screenshot.png';
await page.screenshot({ path: outputPath, fullPage: true });
result.screenshotPath = outputPath;
console.log(`Screenshot saved: ${outputPath}`);
}
// If interactive mode, keep browser open
if (options.interactive) {
console.log('\nInteractive mode - browser will stay open.');
console.log('Press Ctrl+C to close.');
await new Promise(() => {}); // Keep running
}
return result;
} finally {
if (!options.interactive) {
await browser.close();
}
}
}
// Export page for use in other scripts
export async function getPage(options?: {
headless?: boolean;
}): Promise<{ page: Page; browser: BrowserContext }> {
const browser = await launchBrowser({ headless: options?.headless });
const page = await browser.newPage();
return { page, browser };
}
// CLI entry point
async function main() {
const args = parseArgs(process.argv.slice(2), {
string: ['url', 'output'],
boolean: ['screenshot', 'headless', 'interactive', 'help'],
default: {
headless: true,
screenshot: false,
interactive: false,
},
alias: {
u: 'url',
o: 'output',
s: 'screenshot',
h: 'help',
i: 'interactive',
},
});
if (args.help || !args.url) {
console.log(`
Web Browser with Camoufox
Usage:
npx tsx browse.ts --url <url> [options]
Options:
-u, --url <url> URL to navigate to (required)
-s, --screenshot Take a screenshot of the page
-o, --output <path> Output path for screenshot (default: screenshot.png)
--headless <bool> Run in headless mode (default: true)
--wait <ms> Wait time after page load in milliseconds
--timeout <ms> Navigation timeout (default: 60000)
-i, --interactive Keep browser open for manual interaction
-h, --help Show this help message
Examples:
npx tsx browse.ts --url "https://example.com"
npx tsx browse.ts --url "https://example.com" --screenshot --output page.png
npx tsx browse.ts --url "https://example.com" --headless false --interactive
Environment Variables:
CAMOUFOX_PROFILE_PATH Custom profile directory (default: ~/.camoufox-profile/)
CAMOUFOX_HEADLESS Default headless mode (true/false)
`);
process.exit(args.help ? 0 : 1);
}
try {
await browse({
url: args.url,
headless: args.headless,
screenshot: args.screenshot,
output: args.output,
wait: args.wait ? parseInt(args.wait, 10) : undefined,
timeout: args.timeout ? parseInt(args.timeout, 10) : undefined,
interactive: args.interactive,
});
} catch (error) {
console.error('Error:', error instanceof Error ? error.message : error);
process.exit(1);
}
}
// Run if executed directly
const isMainModule = process.argv[1]?.includes('browse.ts');
if (isMainModule) {
main();
}