├── .gitignore ├── package.json ├── chrome-debugging-setup.txt ├── LoginTask.ts ├── cursor-composer.js ├── readme.md └── BaseMonitor.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | dist/ 4 | 5 | # Logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Editor directories and files 10 | .idea/ 11 | .vscode/ 12 | *.swp 13 | *.swo 14 | 15 | # OS generated files 16 | .DS_Store 17 | .DS_Store? 18 | ._* 19 | .Spotlight-V100 20 | .Trashes 21 | ehthumbs.db 22 | Thumbs.db -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cursor-chrome-composer", 3 | "version": "1.0.0", 4 | "description": "A powerful integration between Chrome's DevTools Protocol and Cursor Composer for real-time debugging and monitoring.", 5 | "main": "BaseMonitor.js", 6 | "dependencies": { 7 | "playwright": "^1.50.1" 8 | }, 9 | "scripts": { 10 | "start": "node BaseMonitor.js", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": ["chrome", "debugging", "monitoring", "playwright"], 14 | "author": "", 15 | "license": "ISC" 16 | } 17 | -------------------------------------------------------------------------------- /chrome-debugging-setup.txt: -------------------------------------------------------------------------------- 1 | # Chrome Debugging Instructions: 2 | 1. Install dependencies: 3 | npm install playwright typescript ts-node 4 | npm install --save-dev @types/node 5 | 6 | 2. Run the TypeScript monitor: 7 | ts-node BaseMonitor.ts [URL] [options] 8 | 9 | Available options: 10 | --network, -n : Monitor network requests 11 | --no-clear, --nc : Don't clear console on page reload 12 | --exit-on-error : Exit on encountering errors (good for iterative debugging) 13 | --break-network, --bn: Treat network errors as breaking errors 14 | 15 | Example: 16 | ts-node BaseMonitor.ts http://localhost:8080 --network 17 | 18 | Note: Playwright will automatically handle Chrome instance creation and setup. -------------------------------------------------------------------------------- /LoginTask.ts: -------------------------------------------------------------------------------- 1 | import { BaseMonitor } from "./BaseMonitor"; 2 | 3 | //This is an example on how you can combine playwright automation with the monitor for effective debugging with the agent 4 | 5 | export class LoginTask extends BaseMonitor { 6 | private readonly email = "test@test.com"; 7 | private readonly password = "testtests"; 8 | 9 | constructor() { 10 | super(false); // Always disable network monitoring 11 | } 12 | 13 | async performLogin() { 14 | if (!this.page) throw new Error("Page not initialized"); 15 | 16 | try { 17 | // Wait for login form elements 18 | const emailInput = await this.page.waitForSelector('input[type="email"]', { timeout: 5000 }); 19 | const passwordInput = await this.page.waitForSelector('input[type="password"]', { timeout: 5000 }); 20 | const loginButton = await this.page.waitForSelector('button[type="submit"]', { timeout: 5000 }); 21 | 22 | if (!emailInput || !passwordInput || !loginButton) { 23 | throw new Error("Login form elements not found"); 24 | } 25 | 26 | // Fill in credentials 27 | await emailInput.fill(this.email); 28 | await passwordInput.fill(this.password); 29 | await loginButton.click(); 30 | 31 | console.log("[Login] Credentials entered, attempting login..."); 32 | 33 | // Wait for navigation to admin page 34 | await this.page.waitForURL("**/admin**", { timeout: 10000 }); 35 | console.log("[Navigation] Successfully reached admin page"); 36 | 37 | // Wait for dashboard to load 38 | await this.page.waitForLoadState("networkidle"); 39 | } catch (error: unknown) { 40 | this.handleError("Login Error", error); 41 | } 42 | } 43 | } 44 | 45 | // Example usage: 46 | if (require.main === module) { 47 | const args = process.argv.slice(2); 48 | const url = args[0]; 49 | const loginTask = new LoginTask(); 50 | 51 | (async () => { 52 | await loginTask.initialize(url); 53 | await loginTask.performLogin(); 54 | })().catch((error) => { 55 | console.error("Login task failed:", error); 56 | process.exit(1); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /cursor-composer.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require("ws"); 2 | const https = require("https"); 3 | const http = require("http"); 4 | 5 | async function getAvailablePages() { 6 | return new Promise((resolve, reject) => { 7 | http 8 | .get("http://localhost:9222/json/list", (res) => { 9 | let data = ""; 10 | res.on("data", (chunk) => (data += chunk)); 11 | res.on("end", () => { 12 | try { 13 | const pages = JSON.parse(data); 14 | resolve(pages); 15 | } catch (e) { 16 | reject(e); 17 | } 18 | }); 19 | }) 20 | .on("error", reject); 21 | }); 22 | } 23 | 24 | async function main() { 25 | const pageId = process.argv[2]; 26 | 27 | if (!pageId) { 28 | console.log("No page ID provided. Available pages:"); 29 | const pages = await getAvailablePages(); 30 | pages.forEach((page) => { 31 | console.log(`\nID: ${page.id}`); 32 | console.log(`Title: ${page.title}`); 33 | console.log(`Type: ${page.type}`); 34 | console.log(`URL: ${page.url}`); 35 | console.log("---"); 36 | }); 37 | console.log("\nUsage: node console-monitor.js "); 38 | process.exit(1); 39 | } 40 | 41 | const ws = new WebSocket(`ws://localhost:9222/devtools/page/${pageId}`); 42 | 43 | ws.on("open", function open() { 44 | console.log(`Connected to page ${pageId}`); 45 | 46 | // Enable console API 47 | ws.send( 48 | JSON.stringify({ 49 | id: 1, 50 | method: "Console.enable", 51 | }) 52 | ); 53 | 54 | // Enable runtime 55 | ws.send( 56 | JSON.stringify({ 57 | id: 2, 58 | method: "Runtime.enable", 59 | }) 60 | ); 61 | 62 | // Enable network monitoring 63 | ws.send( 64 | JSON.stringify({ 65 | id: 3, 66 | method: "Network.enable", 67 | }) 68 | ); 69 | }); 70 | 71 | ws.on("message", function incoming(data) { 72 | const message = JSON.parse(data); 73 | 74 | // Handle console messages 75 | if (message.method === "Console.messageAdded") { 76 | const logMessage = message.params.message; 77 | console.log(`[${logMessage.level}] ${logMessage.text}`); 78 | } 79 | 80 | // Handle console API calls 81 | if (message.method === "Runtime.consoleAPICalled") { 82 | const logMessage = message.params; 83 | console.log(`[${logMessage.type}] ${logMessage.args.map((arg) => arg.value).join(" ")}`); 84 | } 85 | 86 | // Handle network requests 87 | if (message.method === "Network.requestWillBeSent") { 88 | const request = message.params; 89 | console.log(`\n[Network Request] ${request.request.method} ${request.request.url}`); 90 | if (request.request.postData) { 91 | console.log(`[Request Data] ${request.request.postData}`); 92 | } 93 | } 94 | 95 | // Handle network responses 96 | if (message.method === "Network.responseReceived") { 97 | const response = message.params; 98 | console.log( 99 | `[Network Response] ${response.response.status} ${response.response.statusText} - ${response.response.url}` 100 | ); 101 | 102 | // Get response body for JSON responses 103 | if (response.response.mimeType === "application/json") { 104 | ws.send( 105 | JSON.stringify({ 106 | id: 4, 107 | method: "Network.getResponseBody", 108 | params: { requestId: response.requestId }, 109 | }) 110 | ); 111 | } 112 | } 113 | 114 | // Handle response body 115 | if (message.id === 4 && message.result) { 116 | try { 117 | const body = JSON.parse(message.result.body); 118 | console.log("[Response Body]", JSON.stringify(body, null, 2)); 119 | } catch (e) { 120 | console.log("[Response Body]", message.result.body); 121 | } 122 | } 123 | 124 | // Handle network errors 125 | if (message.method === "Network.loadingFailed") { 126 | const failure = message.params; 127 | console.log(`[Network Error] Failed to load ${failure.requestId}: ${failure.errorText}`); 128 | } 129 | }); 130 | 131 | ws.on("error", function error(err) { 132 | console.error("WebSocket error:", err); 133 | process.exit(1); 134 | }); 135 | 136 | console.log("Monitoring console logs and network activity... Press Ctrl+C to stop."); 137 | } 138 | 139 | main().catch((err) => { 140 | console.error("Error:", err); 141 | process.exit(1); 142 | }); 143 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Chrome Debug Monitor 2 | 3 | A powerful integration between Chrome's DevTools Protocol and Cursor Composer for real-time debugging and monitoring. 4 | 5 | ## Usage 6 | 7 | ### 1. Create Debug Setup File 8 | 9 | Create a file named `chrome-debugging-setup.txt` with this content: 10 | 11 | ``` 12 | # Chrome Debugging Instructions: 13 | 1. Install dependencies: 14 | npm install playwright 15 | 16 | 2. Run the JavaScript monitor: 17 | node BaseMonitor.js [URL] [options] 18 | 19 | Available options: 20 | --network, -n : Monitor network requests 21 | --no-clear, --nc : Don't clear console on page reload 22 | --exit-on-error : Exit on encountering errors (good for iterative debugging) 23 | --break-network, --bn: Treat network errors as breaking errors 24 | 25 | Example: 26 | node BaseMonitor.js http://localhost:8080 --network 27 | 28 | Note: Playwright will automatically handle Chrome instance creation and setup. 29 | ``` 30 | 31 | ### 2. Install Dependencies 32 | 33 | ```bash 34 | npm install playwright # Includes Playwright for browser automation 35 | ``` 36 | 37 | ### 3. Create BaseMonitor.js 38 | 39 | Create a file named `BaseMonitor.js` with the JavaScript implementation. You can find the complete implementation here: [@BaseMonitor.js](BaseMonitor.js) 40 | 41 | The monitor provides: 42 | 43 | - Playwright-based monitoring 44 | - Comprehensive error handling 45 | - Network request monitoring 46 | - Console logging 47 | - Worker monitoring 48 | - Dialog handling 49 | - Configurable error behaviors 50 | 51 | ### 4. Prompt the agent 52 | 53 | Ask the cursor composer (agent mode) to run debugging per `chrome-debugging-setup.txt` and then the composer has access to your chrome console logs and network requests. 54 | 55 | Important: You must make sure the agent runs the command NOT in the composer chat as an embedded terminal - but as a terminal as a editor, so the application doesn't shut down automatically and you can keep in the context across composers. 56 | 57 | ## Example screenshots 58 | 59 | ![step2](https://github.com/user-attachments/assets/ddeab00b-dc42-40b2-8d0b-de4fb536860d) 60 | 61 | ![step3](https://github.com/user-attachments/assets/0d691b12-977e-42ea-806a-4d1089b08125) 62 | 63 | ## Output Examples 64 | 65 | ### Console Logs 66 | 67 | ``` 68 | [log] NODE_ENV: development 69 | [warning] Logging in because its an existing user 70 | [Request] GET http://localhost:8080/api/customers 71 | [Response] 200 http://localhost:8080/api/customers 72 | [Worker Started] http://localhost:8080/worker.js 73 | [Dialog] alert: Please confirm your action 74 | ``` 75 | 76 | ### Network Requests 77 | 78 | ``` 79 | [Request] GET http://localhost:8080/api/customers 80 | [Response] 200 OK - http://localhost:8080/api/customers 81 | [Response Body] { 82 | "id": 1, 83 | "name": "John Doe", 84 | ... 85 | } 86 | ``` 87 | 88 | ## Configuration 89 | 90 | The JavaScript monitor (`BaseMonitor.js`) provides comprehensive monitoring of: 91 | 92 | - Console logs (all levels) 93 | - Network requests and responses 94 | - Page errors and runtime exceptions 95 | - Web Workers 96 | - Dialog boxes (alerts, confirms, prompts) 97 | - Custom error handling behaviors 98 | 99 | ## Advanced Usage 100 | 101 | ### Monitor Multiple Pages 102 | 103 | You can monitor multiple pages by running multiple instances with different URLs: 104 | 105 | ```bash 106 | node BaseMonitor.js http://localhost:8080 --network 107 | # In another terminal 108 | node BaseMonitor.js http://localhost:3000 --network 109 | ``` 110 | 111 | ### Cleanup 112 | 113 | To close all Chrome instances: 114 | 115 | ```powershell 116 | taskkill /IM chrome.exe /F 117 | ``` 118 | 119 | ## Contributing 120 | 121 | 1. Fork the repository 122 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 123 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 124 | 4. Push to the branch (`git push origin feature/amazing-feature`) 125 | 5. Open a Pull Request 126 | 127 | ## License 128 | 129 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 130 | 131 | ## Acknowledgments 132 | 133 | - Chrome DevTools Protocol 134 | - WebSocket implementation by Node.js 135 | - Chrome Remote Debugging Interface 136 | 137 | ## Support 138 | 139 | For support, please open an issue in the GitHub repository or contact the maintainers. 140 | 141 | ## Security 142 | 143 | Note: This tool is intended for development and debugging purposes only. Do not use remote debugging in production environments. 144 | 145 | ## Roadmap 146 | 147 | - [ ] Add filtering options for network requests 148 | - [ ] Add support for WebSocket traffic monitoring 149 | - [ ] Add export functionality for logs 150 | - [ ] Add custom formatting options 151 | - [ ] Add support for HTTPS debugging 152 | -------------------------------------------------------------------------------- /BaseMonitor.js: -------------------------------------------------------------------------------- 1 | const { chromium } = require('playwright'); 2 | 3 | class BaseMonitor { 4 | constructor( 5 | monitorNetwork = false, 6 | clearConsoleOnReload = true, 7 | exitOnError = false, 8 | treatNetworkErrorsAsBreaking = false 9 | ) { 10 | this.browser = null; 11 | this.context = null; 12 | this.page = null; 13 | this.monitorNetwork = monitorNetwork; 14 | this.clearConsoleOnReload = clearConsoleOnReload; 15 | this.exitOnError = exitOnError; 16 | this.treatNetworkErrorsAsBreaking = treatNetworkErrorsAsBreaking; 17 | } 18 | 19 | async initialize(url = "http://localhost:8080") { 20 | try { 21 | this.browser = await chromium.launch({ 22 | headless: false, 23 | args: ["--remote-debugging-port=9222"], 24 | }); 25 | 26 | this.context = await this.browser.newContext({ 27 | ignoreHTTPSErrors: true, 28 | }); 29 | 30 | this.page = await this.context.newPage(); 31 | this.setupMonitoring(); 32 | await this.page.goto(url); 33 | console.log(`Successfully connected to application at ${url}`); 34 | await this.page.waitForLoadState("networkidle"); 35 | 36 | // Keep the browser open 37 | await new Promise(() => {}); 38 | } catch (error) { 39 | this.handleError("Initialization Error", error); 40 | } 41 | } 42 | 43 | setupMonitoring() { 44 | if (!this.page) return; 45 | 46 | // Monitor console logs 47 | this.page.on("console", (msg) => { 48 | const type = msg.type(); 49 | const text = msg.text(); 50 | 51 | if (type === "error") { 52 | // Handle network-related errors differently based on configuration 53 | if (text.includes("404") || text.includes("Failed to load resource")) { 54 | if (this.treatNetworkErrorsAsBreaking) { 55 | this.handleError("Network Error", new Error(text)); 56 | } else { 57 | console.log(`[${type}] ${text}`); 58 | } 59 | } else { 60 | this.handleError("Console Error", new Error(text)); 61 | } 62 | } else { 63 | console.log(`[${type}] ${text}`); 64 | } 65 | }); 66 | 67 | // Monitor page errors 68 | this.page.on("pageerror", (error) => { 69 | this.handleError("Page Error", error); 70 | }); 71 | 72 | if (this.monitorNetwork) { 73 | // Monitor requests 74 | this.page.on("request", (request) => { 75 | console.log(`[Request] ${request.method()} ${request.url()}`); 76 | const postData = request.postData(); 77 | if (postData) { 78 | console.log(`[Request Data] ${postData}`); 79 | } 80 | }); 81 | 82 | // Monitor responses 83 | this.page.on("response", async (response) => { 84 | const status = response.status(); 85 | const url = response.url(); 86 | console.log(`[Response] ${status} ${url}`); 87 | 88 | if (status >= 400) { 89 | try { 90 | const text = await response.text(); 91 | if (this.treatNetworkErrorsAsBreaking) { 92 | this.handleError("Network Response Error", new Error(`${url}: ${text}`)); 93 | } else { 94 | console.log(`[Response Error] ${url}: ${text}`); 95 | } 96 | } catch (e) { 97 | console.log(`[Response Error] Could not get response text: ${e}`); 98 | } 99 | } 100 | }); 101 | } 102 | 103 | // Clear console on navigation if enabled 104 | this.page.on("load", () => { 105 | if (this.clearConsoleOnReload) { 106 | console.clear(); 107 | } 108 | console.log("\n[Navigation] Page loaded/refreshed\n"); 109 | }); 110 | 111 | // Monitor workers 112 | this.page.on("worker", (worker) => { 113 | console.log(`[Worker Started] ${worker.url()}`); 114 | worker.evaluate(() => { 115 | self.addEventListener("console", (event) => { 116 | console.log(`[Worker Console] ${event.data}`); 117 | }); 118 | }); 119 | }); 120 | 121 | // Monitor dialogs 122 | this.page.on("dialog", async (dialog) => { 123 | console.log(`[Dialog] ${dialog.type()}: ${dialog.message()}`); 124 | await dialog.dismiss(); 125 | }); 126 | } 127 | 128 | handleError(type, error) { 129 | console.error(`\n[BREAKING] ${type}:`); 130 | console.error("Error Message:", error instanceof Error ? error.message : String(error)); 131 | console.error("Stack Trace:", error instanceof Error ? error.stack : "No stack trace available"); 132 | 133 | // Only exit if exitOnError is true 134 | if (this.exitOnError) { 135 | process.exit(1); 136 | } 137 | } 138 | 139 | async cleanup() { 140 | if (this.browser) { 141 | await this.browser.close(); 142 | } 143 | } 144 | } 145 | 146 | // Add main execution block 147 | if (require.main === module) { 148 | const args = process.argv.slice(2); 149 | const url = args[0]; 150 | 151 | // Parse command line flags 152 | const monitorNetwork = args.includes("--network") || args.includes("-n"); 153 | const noClear = args.includes("--no-clear") || args.includes("--nc"); 154 | const exitOnError = args.includes("--exit-on-error") || args.includes("--exit"); 155 | const breakOnNetwork = args.includes("--break-network") || args.includes("--bn"); 156 | 157 | const monitor = new BaseMonitor(monitorNetwork, !noClear, exitOnError, breakOnNetwork); 158 | 159 | (async () => { 160 | try { 161 | await monitor.initialize(url); 162 | } catch (error) { 163 | console.error("Monitor failed to start:", error); 164 | if (exitOnError) { 165 | process.exit(1); 166 | } 167 | } 168 | })(); 169 | } 170 | 171 | module.exports = BaseMonitor; --------------------------------------------------------------------------------