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.
173 lines (172 loc) • 7.98 kB
JavaScript
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
/**
* DevTools Agent Page Generator
*
* This class generates agent-friendly page representations using Chrome DevTools Protocol.
* It creates a unified text representation that matches what humans see, with proper
* form detection and bulk form fill capabilities.
*/
export class DevToolsAgentPageGenerator {
page;
cdpSession = null;
constructor(page) {
this.page = page;
}
/**
* Generate agent page with form detection
*/
async generateAgentPage(enhanced) {
try {
// Debug to file (commented out for production)
// const fs = await import('fs');
const debugLog = (msg) => {
// const timestamp = new Date().toISOString();
// fs.appendFileSync('/tmp/supapup-debug.log', `${timestamp} ${msg}\n`);
// Uncomment above lines to enable file logging for debugging
};
debugLog('🔧 Starting agent page generation...');
// Setup CDP session if not already available
await this.setupCDP();
// Generate the unified representation in browser context with hover detection
debugLog('🔧 Evaluating generator script in browser context...');
let result;
try {
debugLog('About to call page.evaluate...');
result = await this.page.evaluate(() => {
console.log('🔧 Inside browser evaluate - checking generator...');
const generator = window.__DEVTOOLS_AGENT_GENERATOR__;
if (!generator) {
console.error('❌ Generator not found on window object');
console.log('Available window properties:', Object.keys(window).filter(k => k.includes('AGENT') || k.includes('DEVTOOLS')));
throw new Error('DevTools agent generator not available - script injection failed');
}
console.log('✅ Generator found, calling generate()...');
try {
const genResult = generator.generate();
console.log('✅ Generate completed, result type:', typeof genResult);
console.log('✅ Result has content:', typeof genResult?.content);
console.log('✅ Content length:', genResult?.content?.length);
// Explicitly return a serializable object
const returnValue = {
content: genResult.content || '',
elements: genResult.elements || [],
actions: genResult.actions || [],
title: genResult.title || '',
url: genResult.url || '',
_debug: {
hasContent: !!genResult.content,
contentLength: genResult.content?.length || 0,
elementsCount: genResult.elements?.length || 0
}
};
console.log('🔧 Returning serialized result:', returnValue._debug);
return returnValue;
}
catch (genError) {
console.error('❌ Generator.generate() failed:', genError);
console.error('❌ Error stack:', genError.stack);
throw new Error(`Generator execution failed: ${genError.message}`);
}
});
}
catch (evalError) {
debugLog(`page.evaluate error: ${evalError.message}`);
throw evalError;
}
debugLog('🔧 Browser evaluation completed');
debugLog(`🔧 Result type: ${typeof result}`);
debugLog(`🔧 Result is null/undefined: ${result === null || result === undefined}`);
debugLog(`🔧 Result has content property: ${'content' in (result || {})}`);
if (!result) {
throw new Error('Generator returned null/undefined result');
}
if (!result.content) {
console.error('❌ Result object:', JSON.stringify(result, null, 2));
throw new Error('Generator result missing content property');
}
console.log('✅ Agent page generation successful');
return result;
}
catch (error) {
// Try to log to file even on error (commented out for production)
// try {
// const fs = await import('fs');
// fs.appendFileSync('/tmp/supapup-debug.log', `${new Date().toISOString()} ❌ Error: ${error.message}\n${error.stack}\n`);
// } catch (e) {}
console.error('❌ DevTools agent page generation failed:', error);
console.error('❌ Error stack:', error.stack);
throw new Error(`Agent page generation failed: ${error.message}`);
}
}
/**
* Setup Chrome DevTools Protocol session
*/
async setupCDP() {
try {
// Create CDP session if not exists
if (!this.cdpSession) {
this.cdpSession = await this.page.target().createCDPSession();
}
// Enable required domains
await this.cdpSession.send('Runtime.enable');
await this.cdpSession.send('DOM.enable');
await this.cdpSession.send('CSS.enable');
// Inject the generator script into browser context
const script = this.getGeneratorScript();
console.log('🔧 Injecting DevTools agent generator script...');
// Always inject the script for the current page
try {
await this.page.evaluate(script);
console.log('✅ Script evaluated successfully');
}
catch (scriptError) {
console.error('❌ Script evaluation failed:', scriptError);
throw new Error(`Script evaluation failed: ${scriptError}`);
}
// Also set it up for future navigations
await this.page.evaluateOnNewDocument(script);
// Verify the script was injected successfully
const windowProps = await this.page.evaluate(() => {
return {
hasGenerator: typeof window.__DEVTOOLS_AGENT_GENERATOR__ !== 'undefined',
hasAgentPage: typeof window.__AGENT_PAGE__ !== 'undefined'
};
});
if (!windowProps.hasGenerator || !windowProps.hasAgentPage) {
throw new Error(`Script injection verification failed. Window properties: ${JSON.stringify(windowProps)}`);
}
console.log('✅ DevTools agent generator injected successfully');
}
catch (error) {
console.error('❌ CDP setup failed:', error);
throw new Error(`CDP setup failed: ${error}`);
}
}
/**
* Load the external script file for better maintainability
*/
getGeneratorScript() {
// ES module compatible path resolution
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const scriptPath = path.join(__dirname, 'devtools-agent-page-script.js');
return fs.readFileSync(scriptPath, 'utf8');
}
/**
* Cleanup CDP session
*/
async cleanup() {
try {
if (this.cdpSession) {
await this.cdpSession.detach();
this.cdpSession = null;
}
}
catch (error) {
console.error('CDP cleanup error:', error);
}
}
}