diff --git a/README.md b/README.md index 5fe020a..7187a67 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,26 @@ stef-openclaw-skills/ | Skill | Purpose | Path | |---|---|---| | `gitea-api` | Interact with any Gitea instance via REST API (create repos, issues, PRs, releases, branches, clone) without `tea` CLI. | `skills/gitea-api` | +| `web-automation` | Browse and scrape web pages using Playwright with Camoufox anti-detection browser. For automating web workflows, extracting page content to markdown, handling authenticated sessions, or scraping websites with bot protection. | `skills/web-automation` | + +## Skill-Specific Requirements + +### web-automation + +The `web-automation` skill requires Node.js packages to be installed. When compiling OpenClaw with support for this skill, add the required system libraries: + +```bash +# For Playwright + Camoufox browser dependencies +export OPENCLAW_DOCKER_APT_PACKAGES="ffmpeg jq curl libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2" +``` + +After installing the skill, run the first-time setup: + +```bash +cd ~/.openclaw/workspace/skills/web-automation/scripts +pnpm install +npx camoufox-js fetch +``` ## Install Ideas diff --git a/skills/web-automation/.gitignore b/skills/web-automation/.gitignore new file mode 100644 index 0000000..3c45938 --- /dev/null +++ b/skills/web-automation/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +*.log +.DS_Store diff --git a/skills/web-automation/SKILL.md b/skills/web-automation/SKILL.md new file mode 100644 index 0000000..dbb5bdf --- /dev/null +++ b/skills/web-automation/SKILL.md @@ -0,0 +1,48 @@ +--- +name: web-automation +description: Browse and scrape web pages using Playwright with Camoufox anti-detection browser. Use when automating web workflows, extracting page content to markdown, handling authenticated sessions, or scraping websites with bot protection. +--- + +# Web Automation with Camoufox (Codex) + +Automated web browsing and scraping using Playwright with Camoufox anti-detection browser. + +## Requirements + +- Node.js 20+ +- pnpm +- Network access to download browser binaries + +## First-Time Setup + +```bash +cd ~/.openclaw/workspace/skills/web-automation/scripts +pnpm install +npx camoufox-js fetch +``` + +## Prerequisite Check (MANDATORY) + +Before running any automation, verify Playwright + Camoufox dependencies are installed and scripts are configured to use Camoufox. + +```bash +cd ~/.openclaw/workspace/skills/web-automation/scripts +node -e "require.resolve('playwright-core/package.json');require.resolve('camoufox-js/package.json');console.log('OK: playwright-core + camoufox-js installed')" +node -e "const fs=require('fs');const t=fs.readFileSync('browse.ts','utf8');if(!/camoufox-js/.test(t)){throw new Error('browse.ts is not configured for Camoufox')}console.log('OK: Camoufox integration detected in browse.ts')" +``` + +If any check fails, stop and return: + +"Missing dependency/config: web-automation requires `playwright-core` + `camoufox-js` and Camoufox-based scripts. Run setup in this skill, then retry." + +## Quick Reference + +- Browse page: `npx tsx browse.ts --url "https://example.com"` +- Scrape markdown: `npx tsx scrape.ts --url "https://example.com" --mode main --output page.md` +- Authenticate: `npx tsx auth.ts --url "https://example.com/login"` + +## Notes + +- Sessions persist in Camoufox profile storage. +- Use `--wait` for dynamic pages. +- Use `--mode selector --selector "..."` for targeted extraction. diff --git a/skills/web-automation/scripts/auth.ts b/skills/web-automation/scripts/auth.ts new file mode 100644 index 0000000..5c25b98 --- /dev/null +++ b/skills/web-automation/scripts/auth.ts @@ -0,0 +1,575 @@ +#!/usr/bin/env npx tsx + +/** + * Authentication handler for web automation + * Supports generic form login and Microsoft SSO (MSAL) + * + * Usage: + * npx tsx auth.ts --url "https://example.com/login" --type form + * npx tsx auth.ts --url "https://example.com" --type msal + * npx tsx auth.ts --url "https://example.com" --type auto + */ + +import { getPage, launchBrowser } from './browse.js'; +import parseArgs from 'minimist'; +import type { Page, BrowserContext } from 'playwright-core'; +import { createInterface } from 'readline'; + +// Types +type AuthType = 'auto' | 'form' | 'msal'; + +interface AuthOptions { + url: string; + authType: AuthType; + credentials?: { + username: string; + password: string; + }; + headless?: boolean; + timeout?: number; +} + +interface AuthResult { + success: boolean; + finalUrl: string; + authType: AuthType; + message: string; +} + +// Get credentials from environment or options +function getCredentials(options?: { + username?: string; + password?: string; +}): { username: string; password: string } | null { + const username = options?.username || process.env.CAMOUFOX_USERNAME; + const password = options?.password || process.env.CAMOUFOX_PASSWORD; + + if (!username || !password) { + return null; + } + + return { username, password }; +} + +// Prompt user for input (for MFA or credentials) +async function promptUser(question: string, hidden = false): Promise { + const rl = createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => { + if (hidden) { + process.stdout.write(question); + // Note: This is a simple implementation. For production, use a proper hidden input library + } + rl.question(question, (answer) => { + rl.close(); + resolve(answer); + }); + }); +} + +// Detect authentication type from page +async function detectAuthType(page: Page): Promise { + const url = page.url(); + + // Check for Microsoft login + if ( + url.includes('login.microsoftonline.com') || + url.includes('login.live.com') || + url.includes('login.windows.net') + ) { + return 'msal'; + } + + // Check for common form login patterns + const hasLoginForm = await page.evaluate(() => { + const passwordField = document.querySelector( + 'input[type="password"], input[name*="password"], input[id*="password"]' + ); + const usernameField = document.querySelector( + 'input[type="email"], input[type="text"][name*="user"], input[type="text"][name*="email"], input[id*="user"], input[id*="email"]' + ); + return !!(passwordField && usernameField); + }); + + if (hasLoginForm) { + return 'form'; + } + + return 'auto'; +} + +// Handle generic form login +async function handleFormLogin( + page: Page, + credentials: { username: string; password: string }, + timeout: number +): Promise { + console.log('Attempting form login...'); + + // Find and fill username/email field + const usernameSelectors = [ + 'input[type="email"]', + 'input[name*="user" i]', + 'input[name*="email" i]', + 'input[id*="user" i]', + 'input[id*="email" i]', + 'input[autocomplete="username"]', + 'input[type="text"]:first-of-type', + ]; + + let usernameField = null; + for (const selector of usernameSelectors) { + usernameField = await page.$(selector); + if (usernameField) break; + } + + if (!usernameField) { + console.error('Could not find username/email field'); + return false; + } + + await usernameField.fill(credentials.username); + console.log('Filled username field'); + + // Find and fill password field + const passwordSelectors = [ + 'input[type="password"]', + 'input[name*="password" i]', + 'input[id*="password" i]', + 'input[autocomplete="current-password"]', + ]; + + let passwordField = null; + for (const selector of passwordSelectors) { + passwordField = await page.$(selector); + if (passwordField) break; + } + + if (!passwordField) { + console.error('Could not find password field'); + return false; + } + + await passwordField.fill(credentials.password); + console.log('Filled password field'); + + // Check for "Remember me" checkbox and check it + const rememberCheckbox = await page.$( + 'input[type="checkbox"][name*="remember" i], input[type="checkbox"][id*="remember" i]' + ); + if (rememberCheckbox) { + await rememberCheckbox.check(); + console.log('Checked "Remember me" checkbox'); + } + + // Find and click submit button + const submitSelectors = [ + 'button[type="submit"]', + 'input[type="submit"]', + 'button:has-text("Sign in")', + 'button:has-text("Log in")', + 'button:has-text("Login")', + 'button:has-text("Submit")', + '[role="button"]:has-text("Sign in")', + ]; + + let submitButton = null; + for (const selector of submitSelectors) { + submitButton = await page.$(selector); + if (submitButton) break; + } + + if (!submitButton) { + // Try pressing Enter as fallback + await passwordField.press('Enter'); + } else { + await submitButton.click(); + } + + console.log('Submitted login form'); + + // Wait for navigation or error + try { + await page.waitForNavigation({ timeout, waitUntil: 'domcontentloaded' }); + return true; + } catch { + // Check if we're still on login page with error + const errorMessages = await page.$$eval( + '.error, .alert-danger, [role="alert"], .login-error', + (els) => els.map((el) => el.textContent?.trim()).filter(Boolean) + ); + + if (errorMessages.length > 0) { + console.error('Login error:', errorMessages.join(', ')); + return false; + } + + return true; // Might have succeeded without navigation + } +} + +// Handle Microsoft SSO login +async function handleMsalLogin( + page: Page, + credentials: { username: string; password: string }, + timeout: number +): Promise { + console.log('Attempting Microsoft SSO login...'); + + const currentUrl = page.url(); + + // If not already on Microsoft login, wait for redirect + if (!currentUrl.includes('login.microsoftonline.com')) { + try { + await page.waitForURL('**/login.microsoftonline.com/**', { timeout: 10000 }); + } catch { + console.log('Not redirected to Microsoft login'); + return false; + } + } + + // Wait for email input + const emailInput = await page.waitForSelector( + 'input[type="email"], input[name="loginfmt"]', + { timeout } + ); + + if (!emailInput) { + console.error('Could not find email input on Microsoft login'); + return false; + } + + // Fill email and submit + await emailInput.fill(credentials.username); + console.log('Filled email field'); + + const nextButton = await page.$('input[type="submit"], button[type="submit"]'); + if (nextButton) { + await nextButton.click(); + } else { + await emailInput.press('Enter'); + } + + // Wait for password page + try { + await page.waitForSelector( + 'input[type="password"], input[name="passwd"]', + { timeout } + ); + } catch { + // Might be using passwordless auth or different flow + console.log('Password field not found - might be using different auth flow'); + return false; + } + + // Fill password + const passwordInput = await page.$('input[type="password"], input[name="passwd"]'); + if (!passwordInput) { + console.error('Could not find password input'); + return false; + } + + await passwordInput.fill(credentials.password); + console.log('Filled password field'); + + // Submit + const signInButton = await page.$('input[type="submit"], button[type="submit"]'); + if (signInButton) { + await signInButton.click(); + } else { + await passwordInput.press('Enter'); + } + + // Handle "Stay signed in?" prompt + try { + const staySignedInButton = await page.waitForSelector( + 'input[value="Yes"], button:has-text("Yes")', + { timeout: 5000 } + ); + if (staySignedInButton) { + await staySignedInButton.click(); + console.log('Clicked "Stay signed in" button'); + } + } catch { + // Prompt might not appear + } + + // Check for Conditional Access Policy error + const caError = await page.$('text=Conditional Access policy'); + if (caError) { + console.error('Blocked by Conditional Access Policy'); + // Take screenshot for debugging + await page.screenshot({ path: 'ca-policy-error.png' }); + console.log('Screenshot saved: ca-policy-error.png'); + return false; + } + + // Wait for redirect away from Microsoft login + try { + await page.waitForURL( + (url) => !url.href.includes('login.microsoftonline.com'), + { timeout } + ); + return true; + } catch { + return false; + } +} + +// Check if user is already authenticated +async function isAuthenticated(page: Page, targetUrl: string): Promise { + const currentUrl = page.url(); + + // If we're on the target URL (not a login page), we're likely authenticated + if (currentUrl.startsWith(targetUrl)) { + // Check for common login page indicators + const isLoginPage = await page.evaluate(() => { + const loginIndicators = [ + 'input[type="password"]', + 'form[action*="login"]', + 'form[action*="signin"]', + '.login-form', + '#login', + ]; + return loginIndicators.some((sel) => document.querySelector(sel) !== null); + }); + + return !isLoginPage; + } + + return false; +} + +// Main authentication function +export async function authenticate(options: AuthOptions): Promise { + const browser = await launchBrowser({ headless: options.headless ?? true }); + const page = await browser.newPage(); + const timeout = options.timeout ?? 30000; + + try { + // Navigate to URL + console.log(`Navigating to: ${options.url}`); + await page.goto(options.url, { timeout: 60000, waitUntil: 'domcontentloaded' }); + + // Check if already authenticated + if (await isAuthenticated(page, options.url)) { + return { + success: true, + finalUrl: page.url(), + authType: 'auto', + message: 'Already authenticated (session persisted from profile)', + }; + } + + // Get credentials + const credentials = options.credentials + ? options.credentials + : getCredentials(); + + if (!credentials) { + // No credentials - open interactive browser + console.log('\nNo credentials provided. Opening browser for manual login...'); + console.log('Please complete the login process manually.'); + console.log('The session will be saved to your profile.'); + + // Switch to headed mode for manual login + await browser.close(); + const interactiveBrowser = await launchBrowser({ headless: false }); + const interactivePage = await interactiveBrowser.newPage(); + await interactivePage.goto(options.url); + + await promptUser('\nPress Enter when you have completed login...'); + + const finalUrl = interactivePage.url(); + await interactiveBrowser.close(); + + return { + success: true, + finalUrl, + authType: 'auto', + message: 'Manual login completed - session saved to profile', + }; + } + + // Detect auth type if auto + let authType = options.authType; + if (authType === 'auto') { + authType = await detectAuthType(page); + console.log(`Detected auth type: ${authType}`); + } + + // Handle authentication based on type + let success = false; + switch (authType) { + case 'msal': + success = await handleMsalLogin(page, credentials, timeout); + break; + case 'form': + default: + success = await handleFormLogin(page, credentials, timeout); + break; + } + + const finalUrl = page.url(); + + return { + success, + finalUrl, + authType, + message: success + ? `Authentication successful - session saved to profile` + : 'Authentication failed', + }; + } finally { + await browser.close(); + } +} + +// Navigate to authenticated page (handles auth if needed) +export async function navigateAuthenticated( + url: string, + options?: { + credentials?: { username: string; password: string }; + headless?: boolean; + } +): Promise<{ page: Page; browser: BrowserContext }> { + const { page, browser } = await getPage({ headless: options?.headless ?? true }); + + await page.goto(url, { timeout: 60000, waitUntil: 'domcontentloaded' }); + + // Check if we need to authenticate + if (!(await isAuthenticated(page, url))) { + console.log('Session expired or not authenticated. Attempting login...'); + + // Get credentials + const credentials = options?.credentials ?? getCredentials(); + + if (!credentials) { + throw new Error( + 'Authentication required but no credentials provided. ' + + 'Set CAMOUFOX_USERNAME and CAMOUFOX_PASSWORD environment variables.' + ); + } + + // Detect and handle auth + const authType = await detectAuthType(page); + + let success = false; + if (authType === 'msal') { + success = await handleMsalLogin(page, credentials, 30000); + } else { + success = await handleFormLogin(page, credentials, 30000); + } + + if (!success) { + await browser.close(); + throw new Error('Authentication failed'); + } + + // Navigate back to original URL if we were redirected + if (!page.url().startsWith(url)) { + await page.goto(url, { timeout: 60000, waitUntil: 'domcontentloaded' }); + } + } + + return { page, browser }; +} + +// CLI entry point +async function main() { + const args = parseArgs(process.argv.slice(2), { + string: ['url', 'type', 'username', 'password'], + boolean: ['headless', 'help'], + default: { + type: 'auto', + headless: false, // Default to headed for auth so user can see/interact + }, + alias: { + u: 'url', + t: 'type', + h: 'help', + }, + }); + + if (args.help || !args.url) { + console.log(` +Web Authentication Handler + +Usage: + npx tsx auth.ts --url [options] + +Options: + -u, --url URL to authenticate (required) + -t, --type Auth type: auto, form, or msal (default: auto) + --username Username/email (or set CAMOUFOX_USERNAME env var) + --password Password (or set CAMOUFOX_PASSWORD env var) + --headless Run in headless mode (default: false for auth) + -h, --help Show this help message + +Auth Types: + auto Auto-detect authentication type + form Generic username/password form + msal Microsoft SSO (login.microsoftonline.com) + +Environment Variables: + CAMOUFOX_USERNAME Default username/email for authentication + CAMOUFOX_PASSWORD Default password for authentication + +Examples: + # Interactive login (no credentials, opens browser) + npx tsx auth.ts --url "https://example.com/login" + + # Form login with credentials + npx tsx auth.ts --url "https://example.com/login" --type form \\ + --username "user@example.com" --password "secret" + + # Microsoft SSO login + CAMOUFOX_USERNAME=user@company.com CAMOUFOX_PASSWORD=secret \\ + npx tsx auth.ts --url "https://internal.company.com" --type msal + +Notes: + - Session is saved to ~/.camoufox-profile/ for persistence + - After successful auth, subsequent browses will be authenticated + - Use --headless false if you need to handle MFA manually +`); + process.exit(args.help ? 0 : 1); + } + + const authType = args.type as AuthType; + if (!['auto', 'form', 'msal'].includes(authType)) { + console.error(`Invalid auth type: ${authType}. Must be auto, form, or msal.`); + process.exit(1); + } + + try { + const result = await authenticate({ + url: args.url, + authType, + credentials: + args.username && args.password + ? { username: args.username, password: args.password } + : undefined, + headless: args.headless, + }); + + console.log(`\nAuthentication result:`); + console.log(` Success: ${result.success}`); + console.log(` Auth type: ${result.authType}`); + console.log(` Final URL: ${result.finalUrl}`); + console.log(` Message: ${result.message}`); + + process.exit(result.success ? 0 : 1); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : error); + process.exit(1); + } +} + +// Run if executed directly +const isMainModule = process.argv[1]?.includes('auth.ts'); +if (isMainModule) { + main(); +} diff --git a/skills/web-automation/scripts/browse.ts b/skills/web-automation/scripts/browse.ts new file mode 100644 index 0000000..901089b --- /dev/null +++ b/skills/web-automation/scripts/browse.ts @@ -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 { + 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 { + 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 [options] + +Options: + -u, --url URL to navigate to (required) + -s, --screenshot Take a screenshot of the page + -o, --output Output path for screenshot (default: screenshot.png) + --headless Run in headless mode (default: true) + --wait Wait time after page load in milliseconds + --timeout 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(); +} diff --git a/skills/web-automation/scripts/package.json b/skills/web-automation/scripts/package.json new file mode 100644 index 0000000..8468ae3 --- /dev/null +++ b/skills/web-automation/scripts/package.json @@ -0,0 +1,30 @@ +{ + "name": "web-automation-scripts", + "version": "1.0.0", + "description": "Web browsing and scraping scripts using Camoufox", + "type": "module", + "scripts": { + "browse": "tsx browse.ts", + "scrape": "tsx scrape.ts", + "fetch-browser": "npx camoufox-js fetch" + }, + "dependencies": { + "@mozilla/readability": "^0.5.0", + "better-sqlite3": "^12.6.2", + "camoufox-js": "^0.8.5", + "jsdom": "^24.0.0", + "minimist": "^1.2.8", + "playwright-core": "^1.40.0", + "turndown": "^7.1.2", + "turndown-plugin-gfm": "^1.0.2" + }, + "devDependencies": { + "@types/jsdom": "^21.1.6", + "@types/minimist": "^1.2.5", + "@types/turndown": "^5.0.4", + "esbuild": "0.27.0", + "tsx": "^4.7.0", + "typescript": "^5.3.0" + }, + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" +} diff --git a/skills/web-automation/scripts/pnpm-lock.yaml b/skills/web-automation/scripts/pnpm-lock.yaml new file mode 100644 index 0000000..3e349b3 --- /dev/null +++ b/skills/web-automation/scripts/pnpm-lock.yaml @@ -0,0 +1,1616 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^12.6.2 + version: 12.6.2 + camoufox-js: + specifier: ^0.8.5 + version: 0.8.5(playwright-core@1.57.0) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + playwright-core: + specifier: ^1.40.0 + version: 1.57.0 + turndown: + specifier: ^7.1.2 + version: 7.2.2 + turndown-plugin-gfm: + specifier: ^1.0.2 + version: 1.0.2 + devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.7 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/turndown': + specifier: ^5.0.4 + version: 5.0.6 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + +packages: + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.27.0': + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.0': + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.0': + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.0': + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.0': + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.0': + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.0': + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.0': + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.0': + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.0': + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.0': + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.0': + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.0': + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.0': + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.0': + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.0': + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.0': + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.0': + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.0': + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.0': + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.0': + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.0': + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.0': + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.0': + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.0': + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.0': + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + + '@mozilla/readability@0.5.0': + resolution: {integrity: sha512-Z+CZ3QaosfFaTqvhQsIktyGrjFjSC0Fa4EMph4mqKnWhmyoGICsV/8QK+8HpXut6zV7zwfWwqDmEjtk1Qf6EgQ==} + engines: {node: '>=14.0.0'} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@types/jsdom@21.1.7': + resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/node@25.0.6': + resolution: {integrity: sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/turndown@5.0.6': + resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} + + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.9.14: + resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} + hasBin: true + + better-sqlite3@12.6.2: + resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camoufox-js@0.8.5: + resolution: {integrity: sha512-20ihPbspAcOVSUTX9Drxxp0C116DON1n8OVA1eUDglWZiHwiHwFVFOMrIEBwAHMZpU11mIEH/kawJtstRIrDPA==} + engines: {node: '>= 20'} + hasBin: true + peerDependencies: + playwright-core: '*' + + caniuse-lite@1.0.30001764: + resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-europe-js@0.1.2: + resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dot-prop@6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.27.0: + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fingerprint-generator@2.1.79: + resolution: {integrity: sha512-0dr3kTgvRYHleRPp6OBDcPb8amJmOyFr9aOuwnpN6ooWJ5XyT+/aL/SZ6CU4ZrEtzV26EyJ2Lg7PT32a0NdrRA==} + engines: {node: '>=16.0.0'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + generative-bayesian-network@2.1.79: + resolution: {integrity: sha512-aPH+V2wO+HE0BUX1LbsM8Ak99gmV43lgh+D7GDteM0zgnPqiAwcK9JZPxMPZa3aJUleFtFaL1lAei8g9zNrDIA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob@13.0.0: + resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + engines: {node: 20 || >=22} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + header-generator@2.1.79: + resolution: {integrity: sha512-YvHx8teq4QmV5mz7wdPMsj9n1OZBPnZxA4QE+EOrtx7xbmGvd1gBvDNKCb5XqS4GR/TL75MU5hqMqqqANdILRg==} + engines: {node: '>=16.0.0'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + impit-darwin-arm64@0.7.6: + resolution: {integrity: sha512-M7NQXkttyzqilWfzVkNCp7hApT69m0etyJkVpHze4bR5z1kJnHhdsb8BSdDv2dzvZL4u1JyqZNxq+qoMn84eUw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + impit-darwin-x64@0.7.6: + resolution: {integrity: sha512-kikTesWirAwJp9JPxzGLoGVc+heBlEabWS5AhTkQedACU153vmuL90OBQikVr3ul2N0LPImvnuB+51wV0zDE6g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + impit-linux-arm64-gnu@0.7.6: + resolution: {integrity: sha512-H6GHjVr/0lG9VEJr6IHF8YLq+YkSIOF4k7Dfue2ygzUAj1+jZ5ZwnouhG/XrZHYW6EWsZmEAjjRfWE56Q0wDRQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + impit-linux-arm64-musl@0.7.6: + resolution: {integrity: sha512-1sCB/UBVXLZTpGJsXRdNNSvhN9xmmQcYLMWAAB4Itb7w684RHX1pLoCb6ichv7bfAf6tgaupcFIFZNBp3ghmQA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + impit-linux-x64-gnu@0.7.6: + resolution: {integrity: sha512-yYhlRnZ4fhKt8kuGe0JK2WSHc8TkR6BEH0wn+guevmu8EOn9Xu43OuRvkeOyVAkRqvFnlZtMyySUo/GuSLz9Gw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + impit-linux-x64-musl@0.7.6: + resolution: {integrity: sha512-sdGWyu+PCLmaOXy7Mzo4WP61ZLl5qpZ1L+VeXW+Ycazgu0e7ox0NZLdiLRunIrEzD+h0S+e4CyzNwaiP3yIolg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + impit-win32-arm64-msvc@0.7.6: + resolution: {integrity: sha512-sM5deBqo0EuXg5GACBUMKEua9jIau/i34bwNlfrf/Amnw1n0GB4/RkuUh+sKiUcbNAntrRq+YhCq8qDP8IW19w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + impit-win32-x64-msvc@0.7.6: + resolution: {integrity: sha512-ry63ADGLCB/PU/vNB1VioRt2V+klDJ34frJUXUZBEv1kA96HEAg9AxUk+604o+UHS3ttGH2rkLmrbwHOdAct5Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + impit@0.7.6: + resolution: {integrity: sha512-AkS6Gv63+E6GMvBrcRhMmOREKpq5oJ0J5m3xwfkHiEs97UIsbpEqFmW3sFw/sdyOTDGRF5q4EjaLxtb922Ta8g==} + engines: {node: '>= 20'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-standalone-pwa@0.1.1: + resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==} + + jsdom@24.1.3: + resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@2.1.0: + resolution: {integrity: sha512-D4CgpyCt+61f6z2jHjJS1OmZPviAWM57iJ9OKdFFWSNgS7Udj9QVWqyGs/cveVNF57XpZmhSvMdVIV5mjLA7Vg==} + engines: {node: '>=22'} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + engines: {node: 20 || >=22} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + maxmind@5.0.3: + resolution: {integrity: sha512-oMtZwLrsp0LcZehfYKIirtwKMBycMMqMA1/Dc9/BlUqIEtXO75mIzMJ3PYCV1Ji+BpoUCk+lTzRfh9c+ptGdyQ==} + engines: {node: '>=12', npm: '>=6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mmdb-lib@3.0.1: + resolution: {integrity: sha512-dyAyMR+cRykZd1mw5altC9f4vKpCsuywPwo8l/L5fKqDay2zmqT0mF/BvUoXnQiqGn+nceO914rkPKJoyFnGxA==} + engines: {node: '>=10', npm: '>=6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + node-abi@3.85.0: + resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} + engines: {node: '>=10'} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + ow@0.28.2: + resolution: {integrity: sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==} + engines: {node: '>=12'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + playwright-core@1.57.0: + resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} + engines: {node: '>=18'} + hasBin: true + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tiny-lru@11.4.5: + resolution: {integrity: sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw==} + engines: {node: '>=12'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + turndown-plugin-gfm@1.0.2: + resolution: {integrity: sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==} + + turndown@7.2.2: + resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ua-is-frozen@0.1.2: + resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} + + ua-parser-js@2.0.7: + resolution: {integrity: sha512-CFdHVHr+6YfbktNZegH3qbYvYgC7nRNEUm2tk7nSFXSODUu4tDBpaFpP1jdXBUOKKwapVlWRfTtS8bCPzsQ47w==} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vali-date@1.0.0: + resolution: {integrity: sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==} + engines: {node: '>=0.10.0'} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + +snapshots: + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@esbuild/aix-ppc64@0.27.0': + optional: true + + '@esbuild/android-arm64@0.27.0': + optional: true + + '@esbuild/android-arm@0.27.0': + optional: true + + '@esbuild/android-x64@0.27.0': + optional: true + + '@esbuild/darwin-arm64@0.27.0': + optional: true + + '@esbuild/darwin-x64@0.27.0': + optional: true + + '@esbuild/freebsd-arm64@0.27.0': + optional: true + + '@esbuild/freebsd-x64@0.27.0': + optional: true + + '@esbuild/linux-arm64@0.27.0': + optional: true + + '@esbuild/linux-arm@0.27.0': + optional: true + + '@esbuild/linux-ia32@0.27.0': + optional: true + + '@esbuild/linux-loong64@0.27.0': + optional: true + + '@esbuild/linux-mips64el@0.27.0': + optional: true + + '@esbuild/linux-ppc64@0.27.0': + optional: true + + '@esbuild/linux-riscv64@0.27.0': + optional: true + + '@esbuild/linux-s390x@0.27.0': + optional: true + + '@esbuild/linux-x64@0.27.0': + optional: true + + '@esbuild/netbsd-arm64@0.27.0': + optional: true + + '@esbuild/netbsd-x64@0.27.0': + optional: true + + '@esbuild/openbsd-arm64@0.27.0': + optional: true + + '@esbuild/openbsd-x64@0.27.0': + optional: true + + '@esbuild/openharmony-arm64@0.27.0': + optional: true + + '@esbuild/sunos-x64@0.27.0': + optional: true + + '@esbuild/win32-arm64@0.27.0': + optional: true + + '@esbuild/win32-ia32@0.27.0': + optional: true + + '@esbuild/win32-x64@0.27.0': + optional: true + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@mixmark-io/domino@2.2.0': {} + + '@mozilla/readability@0.5.0': {} + + '@sindresorhus/is@4.6.0': {} + + '@types/jsdom@21.1.7': + dependencies: + '@types/node': 25.0.6 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 + + '@types/minimist@1.2.5': {} + + '@types/node@25.0.6': + dependencies: + undici-types: 7.16.0 + + '@types/tough-cookie@4.0.5': {} + + '@types/turndown@5.0.6': {} + + adm-zip@0.5.16: {} + + agent-base@7.1.4: {} + + asynckit@0.4.0: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.9.14: {} + + better-sqlite3@12.6.2: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.14 + caniuse-lite: 1.0.30001764 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + camoufox-js@0.8.5(playwright-core@1.57.0): + dependencies: + adm-zip: 0.5.16 + better-sqlite3: 12.6.2 + commander: 14.0.2 + fingerprint-generator: 2.1.79 + glob: 13.0.0 + impit: 0.7.6 + language-tags: 2.1.0 + maxmind: 5.0.3 + playwright-core: 1.57.0 + progress: 2.0.3 + ua-parser-js: 2.0.7 + xml2js: 0.6.2 + + caniuse-lite@1.0.30001764: {} + + chownr@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@14.0.2: {} + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + delayed-stream@1.0.0: {} + + detect-europe-js@0.1.2: {} + + detect-libc@2.1.2: {} + + dot-prop@6.0.1: + dependencies: + is-obj: 2.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.267: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@6.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.27.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.0 + '@esbuild/android-arm': 0.27.0 + '@esbuild/android-arm64': 0.27.0 + '@esbuild/android-x64': 0.27.0 + '@esbuild/darwin-arm64': 0.27.0 + '@esbuild/darwin-x64': 0.27.0 + '@esbuild/freebsd-arm64': 0.27.0 + '@esbuild/freebsd-x64': 0.27.0 + '@esbuild/linux-arm': 0.27.0 + '@esbuild/linux-arm64': 0.27.0 + '@esbuild/linux-ia32': 0.27.0 + '@esbuild/linux-loong64': 0.27.0 + '@esbuild/linux-mips64el': 0.27.0 + '@esbuild/linux-ppc64': 0.27.0 + '@esbuild/linux-riscv64': 0.27.0 + '@esbuild/linux-s390x': 0.27.0 + '@esbuild/linux-x64': 0.27.0 + '@esbuild/netbsd-arm64': 0.27.0 + '@esbuild/netbsd-x64': 0.27.0 + '@esbuild/openbsd-arm64': 0.27.0 + '@esbuild/openbsd-x64': 0.27.0 + '@esbuild/openharmony-arm64': 0.27.0 + '@esbuild/sunos-x64': 0.27.0 + '@esbuild/win32-arm64': 0.27.0 + '@esbuild/win32-ia32': 0.27.0 + '@esbuild/win32-x64': 0.27.0 + + escalade@3.2.0: {} + + expand-template@2.0.3: {} + + file-uri-to-path@1.0.0: {} + + fingerprint-generator@2.1.79: + dependencies: + generative-bayesian-network: 2.1.79 + header-generator: 2.1.79 + tslib: 2.8.1 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs-constants@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + generative-bayesian-network@2.1.79: + dependencies: + adm-zip: 0.5.16 + tslib: 2.8.1 + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-from-package@0.0.0: {} + + glob@13.0.0: + dependencies: + minimatch: 10.1.1 + minipass: 7.1.2 + path-scurry: 2.0.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + header-generator@2.1.79: + dependencies: + browserslist: 4.28.1 + generative-bayesian-network: 2.1.79 + ow: 0.28.2 + tslib: 2.8.1 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + impit-darwin-arm64@0.7.6: + optional: true + + impit-darwin-x64@0.7.6: + optional: true + + impit-linux-arm64-gnu@0.7.6: + optional: true + + impit-linux-arm64-musl@0.7.6: + optional: true + + impit-linux-x64-gnu@0.7.6: + optional: true + + impit-linux-x64-musl@0.7.6: + optional: true + + impit-win32-arm64-msvc@0.7.6: + optional: true + + impit-win32-x64-msvc@0.7.6: + optional: true + + impit@0.7.6: + optionalDependencies: + impit-darwin-arm64: 0.7.6 + impit-darwin-x64: 0.7.6 + impit-linux-arm64-gnu: 0.7.6 + impit-linux-arm64-musl: 0.7.6 + impit-linux-x64-gnu: 0.7.6 + impit-linux-x64-musl: 0.7.6 + impit-win32-arm64-msvc: 0.7.6 + impit-win32-x64-msvc: 0.7.6 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + is-obj@2.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-standalone-pwa@0.1.1: {} + + jsdom@24.1.3: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + form-data: 4.0.5 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.19.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + language-subtag-registry@0.3.23: {} + + language-tags@2.1.0: + dependencies: + language-subtag-registry: 0.3.23 + + lodash.isequal@4.5.0: {} + + lru-cache@10.4.3: {} + + lru-cache@11.2.4: {} + + math-intrinsics@1.1.0: {} + + maxmind@5.0.3: + dependencies: + mmdb-lib: 3.0.1 + tiny-lru: 11.4.5 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-response@3.1.0: {} + + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mkdirp-classic@0.5.3: {} + + mmdb-lib@3.0.1: {} + + ms@2.1.3: {} + + napi-build-utils@2.0.0: {} + + node-abi@3.85.0: + dependencies: + semver: 7.7.3 + + node-releases@2.0.27: {} + + nwsapi@2.2.23: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + ow@0.28.2: + dependencies: + '@sindresorhus/is': 4.6.0 + callsites: 3.1.0 + dot-prop: 6.0.1 + lodash.isequal: 4.5.0 + vali-date: 1.0.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.4 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + playwright-core@1.57.0: {} + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.85.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + progress@2.0.3: {} + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + querystringify@2.2.0: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + requires-port@1.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + rrweb-cssom@0.7.1: {} + + rrweb-cssom@0.8.0: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sax@1.4.4: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + semver@7.7.3: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-json-comments@2.0.1: {} + + symbol-tree@3.2.4: {} + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tiny-lru@11.4.5: {} + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.0 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + turndown-plugin-gfm@1.0.2: {} + + turndown@7.2.2: + dependencies: + '@mixmark-io/domino': 2.2.0 + + typescript@5.9.3: {} + + ua-is-frozen@0.1.2: {} + + ua-parser-js@2.0.7: + dependencies: + detect-europe-js: 0.1.2 + is-standalone-pwa: 0.1.1 + ua-is-frozen: 0.1.2 + + undici-types@7.16.0: {} + + universalify@0.2.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + util-deprecate@1.0.2: {} + + vali-date@1.0.0: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + wrappy@1.0.2: {} + + ws@8.19.0: {} + + xml-name-validator@5.0.0: {} + + xml2js@0.6.2: + dependencies: + sax: 1.4.4 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xmlchars@2.2.0: {} diff --git a/skills/web-automation/scripts/scan-local-app.ts b/skills/web-automation/scripts/scan-local-app.ts new file mode 100644 index 0000000..cae88e1 --- /dev/null +++ b/skills/web-automation/scripts/scan-local-app.ts @@ -0,0 +1,212 @@ +import { writeFileSync } from 'fs'; +import { getPage } from './browse.js'; + +const baseUrl = 'http://localhost:3000'; +const username = 'analyst@fhb.local'; +const password = process.env.CAMOUFOX_PASSWORD ?? ''; + +const reportPath = '/Users/stefano.fiorini/Documents/projects/fhb-loan-spreading-pilot-a/docs/plans/2026-01-24-financials-analysis-redesign/web-automation-scan.md'; + +type NavResult = { + requestedUrl: string; + url: string; + status: number | null; + title: string; + error?: string; +}; + +async function gotoWithStatus(page: any, url: string): Promise { + const resp = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch((e: unknown) => ({ error: e })); + if (resp?.error) { + return { + requestedUrl: url, + url: page.url(), + status: null, + title: await page.title().catch(() => ''), + error: String(resp.error), + }; + } + + return { + requestedUrl: url, + url: page.url(), + status: resp ? resp.status() : null, + title: await page.title().catch(() => ''), + }; +} + +async function textOrNull(page: any, selector: string): Promise { + const loc = page.locator(selector).first(); + try { + if ((await loc.count()) === 0) return null; + const txt = await loc.textContent(); + return txt ? txt.trim().replace(/\s+/g, ' ') : null; + } catch { + return null; + } +} + +async function main() { + const { page, browser } = await getPage({ headless: true }); + const lines: string[] = []; + + lines.push('# Web Automation Scan (local)'); + lines.push(''); + lines.push(`- Base URL: ${baseUrl}`); + lines.push(`- Timestamp: ${new Date().toISOString()}`); + lines.push(''); + + try { + lines.push('## Login'); + await gotoWithStatus(page, `${baseUrl}/login`); + await page.locator('input[name="email"]').fill(username); + await page.locator('input[name="password"]').fill(password); + await page.locator('button[type="submit"]').click(); + await page.waitForTimeout(2500); + + const cookies = await page.context().cookies(); + const sessionCookie = cookies.find((c: any) => c.name === 'fhb_session'); + lines.push(`- After submit URL: ${page.url()}`); + lines.push(`- Has session cookie (fhb_session): ${Boolean(sessionCookie)}`); + lines.push(''); + + lines.push('## Demo Case'); + const casesNav = await gotoWithStatus(page, `${baseUrl}/cases`); + lines.push(`- GET /cases → status ${casesNav.status ?? 'ERR'}, final ${casesNav.url}`); + + const envCaseId = process.env.SCAN_CASE_ID?.trim() || null; + let selectedCaseId: string | null = envCaseId; + + if (!selectedCaseId) { + const caseLinks = await page.$$eval('a[href^="/cases/"]', (as) => + as + .map((a) => ({ + href: (a as HTMLAnchorElement).getAttribute('href') || '', + text: (a.textContent || '').trim(), + })) + .filter((x) => x.href.includes('/cases/')) + ); + + const preferredTitles = ['Demo - Strong Borrower', 'Demo - Weak Borrower', 'Demo - Incomplete']; + for (const title of preferredTitles) { + const match = caseLinks.find((l) => l.text.includes(title) && l.href.includes('/cases/')); + const href = match?.href ?? ''; + const m = href.match(/\/cases\/([0-9a-f-]{36})/i); + if (m) { + selectedCaseId = m[1]; + break; + } + } + + if (!selectedCaseId) { + const firstHref = + caseLinks.map((l) => l.href).find((h) => /\/cases\/[0-9a-f-]{36}/i.test(h)) ?? null; + const m = firstHref?.match(/\/cases\/([0-9a-f-]{36})/i) ?? null; + selectedCaseId = m?.[1] ?? null; + } + } + + lines.push(`- Selected caseId: ${selectedCaseId ?? '(none found)'}`); + + if (!selectedCaseId) { + lines.push(''); + lines.push('⚠️ Could not find a demo case link on /cases.'); + writeFileSync(reportPath, lines.join('\n') + '\n', 'utf-8'); + return; + } + + const caseBase = `${baseUrl}/cases/${selectedCaseId}/journey`; + + lines.push(''); + lines.push('## Route Checks'); + + const routesToCheck = [ + `${caseBase}`, + `${caseBase}/financials`, + `${caseBase}/financials/income`, + `${caseBase}/analysis`, + `${caseBase}/analysis/configure`, + `${caseBase}/analysis/ai`, + `${caseBase}/analysis/ai/detail`, + `${caseBase}/spreads`, + ]; + + for (const url of routesToCheck) { + const r = await gotoWithStatus(page, url); + const h1 = await textOrNull(page, 'h1'); + const finalPath = r.url.startsWith(baseUrl) ? r.url.slice(baseUrl.length) : r.url; + lines.push(`- ${url.slice(baseUrl.length)} → status ${r.status ?? 'ERR'} (final ${finalPath})${h1 ? `, h1="${h1}"` : ''}`); + } + + lines.push(''); + lines.push('## Spreadsheet Analysis (UI)'); + await gotoWithStatus(page, `${caseBase}/analysis/configure`); + + const runButton = page.locator('button:has-text("Run Analysis")').first(); + const disabled = await runButton.isDisabled().catch(() => true); + lines.push(`- Run button disabled: ${disabled}`); + + if (!disabled) { + await runButton.click(); + + const resultsWait = page + .waitForURL('**/journey/analysis/results**', { timeout: 180000 }) + .then(() => 'results' as const); + const errorWait = page + .locator('[role="alert"]') + .filter({ hasText: 'Error' }) + .first() + .waitFor({ timeout: 180000 }) + .then(() => 'error' as const); + + const outcome = await Promise.race([resultsWait, errorWait]).catch(() => 'timeout' as const); + + if (outcome === 'results') { + await page.waitForTimeout(1500); + lines.push(`- Results URL: ${page.url().replace(baseUrl, '')}`); + + const downloadHref = await page + .locator('a[href*="/journey/analysis/download"]') + .first() + .getAttribute('href') + .catch(() => null); + + if (downloadHref) { + const dlUrl = downloadHref.startsWith('http') ? downloadHref : `${baseUrl}${downloadHref}`; + const dlResp = await page.goto(dlUrl, { waitUntil: 'commit', timeout: 60000 }).catch(() => null); + lines.push( + `- Download route status: ${dlResp?.status() ?? 'ERR'} (Content-Type: ${dlResp?.headers()?.['content-type'] ?? 'n/a'})` + ); + } else { + lines.push('- Download link not found on results page'); + } + } else if (outcome === 'error') { + const errorText = await page + .locator('[role="alert"]') + .first() + .textContent() + .then((t: string | null) => (t ? t.trim().replace(/\\s+/g, ' ') : null)) + .catch(() => null); + lines.push(`- Stayed on configure page; saw error callout: ${errorText ?? '(unable to read)'}`); + lines.push('- Skipping download check because analysis did not complete.'); + } else { + lines.push('- Timed out waiting for results or error after clicking Run Analysis.'); + } + } else { + lines.push('- Skipped running analysis because Run button was disabled.'); + } + + lines.push(''); + lines.push('## Notes'); + lines.push('- This scan avoids scraping financial values; it records route availability and basic headings.'); + + writeFileSync(reportPath, lines.join('\n') + '\n', 'utf-8'); + } finally { + await browser.close(); + } +} + +main().catch((err) => { + console.error(err); + process.exitCode = 1; +}); diff --git a/skills/web-automation/scripts/scrape.ts b/skills/web-automation/scripts/scrape.ts new file mode 100644 index 0000000..0820de0 --- /dev/null +++ b/skills/web-automation/scripts/scrape.ts @@ -0,0 +1,351 @@ +#!/usr/bin/env npx tsx + +/** + * Web scraper that extracts content to markdown + * + * Usage: + * npx tsx scrape.ts --url "https://example.com" --mode main + * npx tsx scrape.ts --url "https://example.com" --mode full --output page.md + * npx tsx scrape.ts --url "https://example.com" --mode selector --selector ".content" + */ + +import TurndownService from 'turndown'; +import * as turndownPluginGfm from 'turndown-plugin-gfm'; +import { Readability } from '@mozilla/readability'; +import { JSDOM } from 'jsdom'; +import { writeFileSync } from 'fs'; +import parseArgs from 'minimist'; +import { getPage } from './browse.js'; + +// Types +type ScrapeMode = 'main' | 'full' | 'selector'; + +interface ScrapeOptions { + url: string; + mode: ScrapeMode; + selector?: string; + output?: string; + includeLinks?: boolean; + includeTables?: boolean; + includeImages?: boolean; + headless?: boolean; + wait?: number; +} + +interface ScrapeResult { + title: string; + url: string; + markdown: string; + byline?: string; + excerpt?: string; +} + +// Configure Turndown for markdown conversion +function createTurndownService(options: { + includeLinks?: boolean; + includeTables?: boolean; + includeImages?: boolean; +}): TurndownService { + const turndown = new TurndownService({ + headingStyle: 'atx', + hr: '---', + bulletListMarker: '-', + codeBlockStyle: 'fenced', + fence: '```', + emDelimiter: '*', + strongDelimiter: '**', + linkStyle: 'inlined', + }); + + // Add GFM support (tables, strikethrough, task lists) + turndown.use(turndownPluginGfm.gfm); + + // Custom rule for code blocks with language detection + turndown.addRule('codeBlockWithLanguage', { + filter: (node) => { + return ( + node.nodeName === 'PRE' && + node.firstChild?.nodeName === 'CODE' + ); + }, + replacement: (_content, node) => { + const codeNode = node.firstChild as HTMLElement; + const className = codeNode.getAttribute('class') || ''; + const langMatch = className.match(/language-(\w+)/); + const lang = langMatch ? langMatch[1] : ''; + const code = codeNode.textContent || ''; + return `\n\n\`\`\`${lang}\n${code}\n\`\`\`\n\n`; + }, + }); + + // Remove images if not included + if (!options.includeImages) { + turndown.addRule('removeImages', { + filter: 'img', + replacement: () => '', + }); + } + + // Remove links but keep text if not included + if (!options.includeLinks) { + turndown.addRule('removeLinks', { + filter: 'a', + replacement: (content) => content, + }); + } + + // Remove script, style, nav, footer, aside elements + turndown.remove(['script', 'style', 'nav', 'footer', 'aside', 'noscript']); + + return turndown; +} + +// Extract main content using Readability +function extractMainContent(html: string, url: string): { + content: string; + title: string; + byline?: string; + excerpt?: string; +} { + const dom = new JSDOM(html, { url }); + const reader = new Readability(dom.window.document); + const article = reader.parse(); + + if (!article) { + throw new Error('Could not extract main content from page'); + } + + return { + content: article.content, + title: article.title, + byline: article.byline || undefined, + excerpt: article.excerpt || undefined, + }; +} + +// Scrape a URL and return markdown +export async function scrape(options: ScrapeOptions): Promise { + const { page, browser } = await getPage({ headless: options.headless ?? true }); + + try { + // Navigate to URL + console.log(`Navigating to: ${options.url}`); + await page.goto(options.url, { + timeout: 60000, + waitUntil: 'domcontentloaded', + }); + + // Wait if specified + if (options.wait) { + console.log(`Waiting ${options.wait}ms for dynamic content...`); + await page.waitForTimeout(options.wait); + } + + const pageTitle = await page.title(); + const pageUrl = page.url(); + + let html: string; + let title = pageTitle; + let byline: string | undefined; + let excerpt: string | undefined; + + // Get HTML based on mode + switch (options.mode) { + case 'main': { + // Get full page HTML and extract with Readability + const fullHtml = await page.content(); + const extracted = extractMainContent(fullHtml, pageUrl); + html = extracted.content; + title = extracted.title || pageTitle; + byline = extracted.byline; + excerpt = extracted.excerpt; + break; + } + + case 'selector': { + if (!options.selector) { + throw new Error('Selector mode requires --selector option'); + } + const element = await page.$(options.selector); + if (!element) { + throw new Error(`Selector not found: ${options.selector}`); + } + html = await element.innerHTML(); + break; + } + + case 'full': + default: { + // Get body content, excluding common non-content elements + html = await page.evaluate(() => { + // Remove common non-content elements + const selectorsToRemove = [ + 'script', 'style', 'noscript', 'iframe', + 'nav', 'header', 'footer', '.cookie-banner', + '.advertisement', '.ads', '#ads', '.social-share', + '.comments', '#comments', '.sidebar' + ]; + + selectorsToRemove.forEach(selector => { + document.querySelectorAll(selector).forEach(el => el.remove()); + }); + + return document.body.innerHTML; + }); + break; + } + } + + // Convert to markdown + const turndown = createTurndownService({ + includeLinks: options.includeLinks ?? true, + includeTables: options.includeTables ?? true, + includeImages: options.includeImages ?? false, + }); + + let markdown = turndown.turndown(html); + + // Add title as H1 if not already present + if (!markdown.startsWith('# ')) { + markdown = `# ${title}\n\n${markdown}`; + } + + // Add metadata header + const metadataLines = [ + ``, + byline ? `` : null, + excerpt ? `` : null, + ``, + '', + ].filter(Boolean); + + markdown = metadataLines.join('\n') + '\n' + markdown; + + // Clean up excessive whitespace + markdown = markdown + .replace(/\n{4,}/g, '\n\n\n') + .replace(/[ \t]+$/gm, '') + .trim(); + + const result: ScrapeResult = { + title, + url: pageUrl, + markdown, + byline, + excerpt, + }; + + // Save to file if output specified + if (options.output) { + writeFileSync(options.output, markdown, 'utf-8'); + console.log(`Markdown saved to: ${options.output}`); + } + + return result; + } finally { + await browser.close(); + } +} + +// CLI entry point +async function main() { + const args = parseArgs(process.argv.slice(2), { + string: ['url', 'mode', 'selector', 'output'], + boolean: ['headless', 'links', 'tables', 'images', 'help'], + default: { + mode: 'main', + headless: true, + links: true, + tables: true, + images: false, + }, + alias: { + u: 'url', + m: 'mode', + s: 'selector', + o: 'output', + h: 'help', + }, + }); + + if (args.help || !args.url) { + console.log(` +Web Scraper - Extract content to Markdown + +Usage: + npx tsx scrape.ts --url [options] + +Options: + -u, --url URL to scrape (required) + -m, --mode Scrape mode: main, full, or selector (default: main) + -s, --selector CSS selector for selector mode + -o, --output Output file path for markdown + --headless Run in headless mode (default: true) + --wait Wait time for dynamic content + --links Include links in output (default: true) + --tables Include tables in output (default: true) + --images Include images in output (default: false) + -h, --help Show this help message + +Scrape Modes: + main Extract main article content using Readability (best for articles) + full Full page content with common elements removed + selector Extract specific element by CSS selector + +Examples: + npx tsx scrape.ts --url "https://docs.example.com/guide" --mode main + npx tsx scrape.ts --url "https://example.com" --mode full --output page.md + npx tsx scrape.ts --url "https://example.com" --mode selector --selector ".api-docs" + npx tsx scrape.ts --url "https://example.com" --mode main --no-links --output clean.md + +Output Format: + - GitHub Flavored Markdown (tables, strikethrough, task lists) + - Proper heading hierarchy + - Code blocks with language detection + - Metadata comments at top (source URL, date) +`); + process.exit(args.help ? 0 : 1); + } + + const mode = args.mode as ScrapeMode; + if (!['main', 'full', 'selector'].includes(mode)) { + console.error(`Invalid mode: ${mode}. Must be main, full, or selector.`); + process.exit(1); + } + + try { + const result = await scrape({ + url: args.url, + mode, + selector: args.selector, + output: args.output, + includeLinks: args.links, + includeTables: args.tables, + includeImages: args.images, + headless: args.headless, + wait: args.wait ? parseInt(args.wait, 10) : undefined, + }); + + // Print result summary + console.log(`\nScrape complete:`); + console.log(` Title: ${result.title}`); + console.log(` URL: ${result.url}`); + if (result.byline) console.log(` Author: ${result.byline}`); + console.log(` Markdown length: ${result.markdown.length} chars`); + + // Print markdown if not saved to file + if (!args.output) { + console.log('\n--- Markdown Output ---\n'); + console.log(result.markdown); + } + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : error); + process.exit(1); + } +} + +// Run if executed directly +const isMainModule = process.argv[1]?.includes('scrape.ts'); +if (isMainModule) { + main(); +} diff --git a/skills/web-automation/scripts/test-full.ts b/skills/web-automation/scripts/test-full.ts new file mode 100644 index 0000000..42b30f4 --- /dev/null +++ b/skills/web-automation/scripts/test-full.ts @@ -0,0 +1,39 @@ +import { Camoufox } from 'camoufox-js'; +import { homedir } from 'os'; +import { join } from 'path'; +import { mkdirSync, existsSync } from 'fs'; + +async function test() { + const profilePath = join(homedir(), '.camoufox-profile'); + if (!existsSync(profilePath)) { + mkdirSync(profilePath, { recursive: true }); + } + + console.log('Profile path:', profilePath); + console.log('Launching with full options...'); + + const browser = await Camoufox({ + headless: true, + user_data_dir: profilePath, + // humanize: 1.5, // Test without this first + // geoip: true, // Test without this first + // enable_cache: true, + // block_webrtc: false, + }); + + console.log('Browser launched'); + const page = await browser.newPage(); + console.log('Page created'); + + await page.goto('https://github.com', { timeout: 30000 }); + console.log('Navigated to:', page.url()); + console.log('Title:', await page.title()); + + await page.screenshot({ path: '/tmp/github-test.png' }); + console.log('Screenshot saved'); + + await browser.close(); + console.log('Done'); +} + +test().catch(console.error); diff --git a/skills/web-automation/scripts/test-minimal.ts b/skills/web-automation/scripts/test-minimal.ts new file mode 100644 index 0000000..8f2f90a --- /dev/null +++ b/skills/web-automation/scripts/test-minimal.ts @@ -0,0 +1,22 @@ +import { Camoufox } from 'camoufox-js'; + +async function test() { + console.log('Launching Camoufox with minimal config...'); + + const browser = await Camoufox({ + headless: true, + }); + + console.log('Browser launched'); + const page = await browser.newPage(); + console.log('Page created'); + + await page.goto('https://example.com', { timeout: 30000 }); + console.log('Navigated to:', page.url()); + console.log('Title:', await page.title()); + + await browser.close(); + console.log('Done'); +} + +test().catch(console.error); diff --git a/skills/web-automation/scripts/test-profile.ts b/skills/web-automation/scripts/test-profile.ts new file mode 100644 index 0000000..9afd227 --- /dev/null +++ b/skills/web-automation/scripts/test-profile.ts @@ -0,0 +1,32 @@ +import { Camoufox } from 'camoufox-js'; +import { homedir } from 'os'; +import { join } from 'path'; +import { mkdirSync, existsSync } from 'fs'; + +async function test() { + const profilePath = join(homedir(), '.camoufox-profile'); + if (!existsSync(profilePath)) { + mkdirSync(profilePath, { recursive: true }); + } + + console.log('Profile path:', profilePath); + console.log('Launching with user_data_dir...'); + + const browser = await Camoufox({ + headless: true, + user_data_dir: profilePath, + }); + + console.log('Browser launched'); + const page = await browser.newPage(); + console.log('Page created'); + + await page.goto('https://example.com', { timeout: 30000 }); + console.log('Navigated to:', page.url()); + console.log('Title:', await page.title()); + + await browser.close(); + console.log('Done'); +} + +test().catch(console.error); diff --git a/skills/web-automation/scripts/tmp-extract-firsthorizon-colors.ts b/skills/web-automation/scripts/tmp-extract-firsthorizon-colors.ts new file mode 100644 index 0000000..414dfff --- /dev/null +++ b/skills/web-automation/scripts/tmp-extract-firsthorizon-colors.ts @@ -0,0 +1,78 @@ +import { getPage } from './browse.js'; + +type Extracted = { + title: string; + url: string; + colorVars: Array<[string, string]>; + samples: Record; +}; + +function isColorValue(value: string) { + return /#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(value) || /\brgb\(|\bhsl\(/i.test(value); +} + +async function main() { + const url = process.argv[2] ?? 'https://www.firsthorizon.com'; + + const { page, browser } = await getPage({ headless: true }); + try { + await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(5000); + + const data = await page.evaluate(`(() => { + const rootStyles = getComputedStyle(document.documentElement); + const vars = {}; + for (let i = 0; i < rootStyles.length; i++) { + const prop = rootStyles[i]; + if (prop && prop.startsWith('--')) { + vars[prop] = rootStyles.getPropertyValue(prop).trim(); + } + } + + const pick = (selector) => { + const el = document.querySelector(selector); + if (!el) return null; + const cs = getComputedStyle(el); + return { + background: cs.backgroundColor, + color: cs.color, + border: cs.borderColor, + }; + }; + + return { + title: document.title, + url: location.href, + vars, + samples: { + body: pick('body'), + header: pick('header'), + nav: pick('nav'), + primaryButton: pick('button, [role="button"], a[role="button"], a.button, .button'), + link: pick('a'), + }, + }; + })()`); + + const entries = Object.entries(data.vars) as Array<[string, string]>; + const colorVars = entries + .filter(([, v]) => v && isColorValue(v)) + .sort((a, b) => a[0].localeCompare(b[0])); + + const out: Extracted = { + title: data.title, + url: data.url, + colorVars, + samples: data.samples, + }; + + process.stdout.write(JSON.stringify(out, null, 2)); + } finally { + await browser.close(); + } +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/skills/web-automation/scripts/tsconfig.json b/skills/web-automation/scripts/tsconfig.json new file mode 100644 index 0000000..4c23583 --- /dev/null +++ b/skills/web-automation/scripts/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/skills/web-automation/scripts/turndown-plugin-gfm.d.ts b/skills/web-automation/scripts/turndown-plugin-gfm.d.ts new file mode 100644 index 0000000..316bed1 --- /dev/null +++ b/skills/web-automation/scripts/turndown-plugin-gfm.d.ts @@ -0,0 +1,8 @@ +declare module 'turndown-plugin-gfm' { + import TurndownService from 'turndown'; + + export function gfm(turndownService: TurndownService): void; + export function strikethrough(turndownService: TurndownService): void; + export function tables(turndownService: TurndownService): void; + export function taskListItems(turndownService: TurndownService): void; +}