Add web-automation skill variants and inline prerequisite checks
This commit is contained in:
195
skills/web-automation/opencode/scripts/browse.ts
Normal file
195
skills/web-automation/opencode/scripts/browse.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/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();
|
||||
}
|
||||
Reference in New Issue
Block a user