UNPKG

supapup

Version:

⚡ Lightning-fast MCP browser dev tool. Navigate → Get instant structured data. No screenshots needed! Puppeteer: 📸 → CSS selectors → JS eval. Supapup: semantic IDs ready to use. 10x faster, 90% fewer tokens.

231 lines (228 loc) • 8.82 kB
#!/usr/bin/env node /** * CLI interface for Supapup - run MCP tools directly from command line */ import puppeteer from 'puppeteer'; import { DevToolsAgentPageGenerator } from './../generators/devtools-agent-page-generator.js'; import { NavigationMonitor } from './../monitors/navigation-monitor.js'; import { PageSettleDetector } from './page-settle-detector.js'; class SupapupCLI { browser = null; page = null; async run(args) { const command = args[0]; try { // Special server mode - keeps running for multiple commands if (command === 'server') { console.log('Starting Supapup in server mode. Commands:'); console.log(' navigate <url>'); console.log(' execute <actionId> [value]'); console.log(' remap'); console.log(' exit\n'); await this.serverMode(); return; } switch (command) { case 'navigate': await this.navigate(args[1]); break; case 'execute': case 'action': await this.executeAction(args[1], args[2]); break; case 'remap': await this.remapPage(); break; case 'screenshot': await this.screenshot(args[1] || 'screenshot.png'); break; case 'close': await this.close(); break; default: console.log(` Supapup CLI - Direct command line interface Usage: supapup navigate <url> Navigate to URL and generate agent page supapup execute <actionId> [value] Execute an action on current page supapup remap Re-map current page after changes supapup screenshot [filename] Take a screenshot supapup close Close browser Examples: supapup navigate https://google.com supapup execute form-q-textarea "search query" supapup execute form-btnk-submit supapup remap `); } } catch (error) { // console.error('Error:', error); } } async ensureBrowser() { if (!this.browser) { try { // Try to connect to existing browser first console.log('Connecting to existing browser...'); this.browser = await puppeteer.connect({ browserURL: 'http://127.0.0.1:9222' }); const pages = await this.browser.pages(); this.page = pages.find(p => p.url() !== 'about:blank') || pages[0]; if (this.page) { console.log(`Connected to: ${this.page.url()}`); } } catch (e) { // Launch new browser if connection fails console.log('Launching new browser...'); this.browser = await puppeteer.launch({ headless: false, args: ['--remote-debugging-port=9222'] }); const pages = await this.browser.pages(); this.page = pages[0] || await this.browser.newPage(); } } } async navigate(url) { await this.ensureBrowser(); console.log(`Navigating to ${url}...`); await this.page.goto(url, { waitUntil: 'domcontentloaded' }); await new Promise(resolve => setTimeout(resolve, 1000)); // Use the new DevToolsAgentPageGenerator const generator = new DevToolsAgentPageGenerator(this.page); const result = await generator.generateAgentPage(); console.log(`\nFound ${result.elements.length} elements\n`); // Display the agent page console.log(result.content); // Note: Interaction handler already injected by DevToolsAgentPageGenerator console.log('\n✅ Page ready for interaction'); } async executeAction(actionId, value) { await this.ensureBrowser(); if (!this.page || this.page.url() === 'about:blank') { // console.error('No page loaded. Run navigate first.'); return; } const originalUrl = this.page.url(); console.log(`Executing action: ${actionId}${value ? ` with value "${value}"` : ''}...`); // Check if agent page is initialized const hasAgentPage = await this.page.evaluate(() => { return !!window.__AGENT_PAGE__; }); if (!hasAgentPage) { console.log('Agent page not initialized, setting up...'); await this.remapPage(); } // Execute the action const result = await this.page.evaluate((id, val) => { const agentPage = window.__AGENT_PAGE__; if (!agentPage?.execute) { throw new Error('Agent page not initialized'); } return agentPage.execute(id, val ? { value: val } : {}); }, actionId, value); console.log('Action result:', result); // Check if this action might cause changes const shouldWait = actionId.includes('submit') || actionId.includes('search') || actionId.includes('button'); if (shouldWait) { console.log('Waiting for page changes...'); // Wait a bit for any navigation/changes to start await new Promise(resolve => setTimeout(resolve, 500)); // Check for navigation const navCheck = await NavigationMonitor.checkForNavigation(this.page, originalUrl); if (navCheck.navigated) { console.log(`\n⚠️ Navigation detected to: ${navCheck.newUrl}`); if (navCheck.isCaptcha) { console.log('This appears to be a CAPTCHA/verification page'); return; } // Re-inject and remap for new page await new Promise(resolve => setTimeout(resolve, 1000)); await this.remapPage(); } else { // Wait for DOM changes using PageSettleDetector const detector = new PageSettleDetector(this.page); const settleResult = await detector.waitForPageSettle({ domIdleTime: 500, networkIdleTime: 500, globalTimeout: 3000 }); if (settleResult.hasChanges || settleResult.navigated) { console.log('DOM changes detected, remapping...'); await this.remapPage(); } } } } async remapPage() { if (!this.page) { // console.error('No page loaded.'); return; } console.log('Remapping page...'); // Use the new DevToolsAgentPageGenerator for remapping const generator = new DevToolsAgentPageGenerator(this.page); const result = await generator.generateAgentPage(); console.log(`\nFound ${result.elements.length} elements\n`); // Display updated agent page console.log(result.content); } async screenshot(filename) { if (!this.page) { // console.error('No page loaded.'); return; } await this.page.screenshot({ path: filename, fullPage: false }); console.log(`Screenshot saved to ${filename}`); } async close() { if (this.browser) { await this.browser.close(); console.log('Browser closed'); } } async serverMode() { const readline = await import('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: 'supapup> ' }); rl.prompt(); rl.on('line', async (line) => { const parts = line.trim().split(' '); const cmd = parts[0]; if (cmd === 'exit' || cmd === 'quit') { await this.close(); rl.close(); return; } try { await this.run(parts); } catch (error) { // console.error('Error:', error); } rl.prompt(); }); rl.on('close', () => { console.log('\nGoodbye!'); process.exit(0); }); } } // Run CLI const cli = new SupapupCLI(); cli.run(process.argv.slice(2)).then(() => { // Exit after command completes (browser stays open) process.exit(0); }).catch(error => { // console.error('Fatal error:', error); process.exit(1); });