diff --git a/CLAUDE.md b/CLAUDE.md index 121857e..36884f4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,8 +18,8 @@ The system uses a hybrid intelligence architecture: ### Core Components -- `background.js:44-213` - Defines 8 MCP tools for page analysis, content extraction, and element interaction -- `content.js:4-50` - Pattern database with confidence-scored selectors for known sites +- `background.js:44-213` - Defines 17 MCP tools for page analysis, content extraction, element interaction, tab management, and data access +- `content.js:4-95` - Enhanced pattern database with confidence-scored selectors for known sites - `server.js:14-143` - MCP protocol implementation with tool registration and WebSocket handling ## Development Commands @@ -44,7 +44,7 @@ curl http://localhost:3001/health # Check server and extension status ## MCP Tool Categories -### Core Automation Tools (6 tools) +### Web Browser Automation Tools (8 tools) - `page_analyze` - **Two-phase intelligent analysis** with element state detection - Phase 1 (`discover`): Quick scan with element state (enabled/disabled, clickable) - Phase 2 (`detailed`): Full analysis with element fingerprinting and interaction readiness @@ -58,19 +58,23 @@ curl http://localhost:3001/health # Check server and extension status - Natural focus sequence: click → focus → fill for modern web apps - Comprehensive event simulation (beforeinput, input, change, composition) - Validation of successful fill with actual value verification +- `element_get_state` - Get detailed element state (disabled, clickable, focusable, empty) - `page_navigate` - Navigate with optional element wait conditions - `page_wait_for` - Wait for elements or text to appear +- `page_scroll` - Scroll pages in various directions -### Element State Tools (1 tool) -- `element_get_state` - Get detailed element state (disabled, clickable, focusable, empty) +### Tab Management Tools (4 tools) +- `tab_create` - Create new tabs with advanced options +- `tab_close` - Close tabs with flexible targeting +- `tab_list` - Get comprehensive tab information +- `tab_switch` - Switch between tabs intelligently -### Analytics Tools (2 tools) -- `get_analytics` - Token usage analytics and performance metrics -- `clear_analytics` - Reset performance tracking data - -### Legacy Tools (2 tools) -- `browser_navigate` - Basic navigation (compatibility) -- `browser_execute_script` - JavaScript execution with CSP fallbacks +### Browser Data Access Tools (5 tools) +- `get_bookmarks` - Get all bookmarks or search for specific ones +- `add_bookmark` - Add new bookmarks with folder support +- `get_history` - Search browser history with comprehensive filters +- `get_selected_text` - Get currently selected text with rich metadata +- `get_page_links` - Get all hyperlinks with filtering options ## Key Implementation Details diff --git a/opendia-extension/.DS_Store b/opendia-extension/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/opendia-extension/.DS_Store differ diff --git a/opendia-extension/icon-128.png b/opendia-extension/icon-128.png new file mode 100644 index 0000000..c528604 Binary files /dev/null and b/opendia-extension/icon-128.png differ diff --git a/opendia-extension/icon-16.png b/opendia-extension/icon-16.png new file mode 100644 index 0000000..1bf31a6 Binary files /dev/null and b/opendia-extension/icon-16.png differ diff --git a/opendia-extension/icon-32.png b/opendia-extension/icon-32.png new file mode 100644 index 0000000..09e0102 Binary files /dev/null and b/opendia-extension/icon-32.png differ diff --git a/opendia-extension/icon-48.png b/opendia-extension/icon-48.png new file mode 100644 index 0000000..f971951 Binary files /dev/null and b/opendia-extension/icon-48.png differ diff --git a/opendia-extension/logo.mp4 b/opendia-extension/logo.mp4 new file mode 100644 index 0000000..a8a50aa Binary files /dev/null and b/opendia-extension/logo.mp4 differ diff --git a/opendia-extension/manifest.json b/opendia-extension/manifest.json index f3b1106..86e56ce 100644 --- a/opendia-extension/manifest.json +++ b/opendia-extension/manifest.json @@ -1,8 +1,14 @@ { "manifest_version": 3, - "name": "OpenDia Enhanced Browser Automation", - "version": "2.0.0", - "description": "Enhanced browser automation through Model Context Protocol with pattern database and semantic analysis", + "name": "OpenDia", + "version": "1.0.0", + "description": "Browser automation through Model Context Protocol", + "icons": { + "16": "icon-16.png", + "32": "icon-32.png", + "48": "icon-48.png", + "128": "icon-128.png" + }, "permissions": [ "tabs", "activeTab", @@ -21,7 +27,7 @@ }, "action": { "default_popup": "popup.html", - "default_title": "OpenDia Enhanced Browser Automation" + "default_title": "OpenDia" }, "content_scripts": [ { diff --git a/opendia-extension/popup.html b/opendia-extension/popup.html index e578516..4afc4d7 100644 --- a/opendia-extension/popup.html +++ b/opendia-extension/popup.html @@ -2,7 +2,7 @@ - OpenDia Browser Bridge + OpenDia
- -

OpenDia Browser Bridge

+ +

OpenDia

- Checking connection... + + Checking connection... + + Make sure your MCP server is connected. + If it's the case, click on Reconnect. + If it still don't work, kill your 3000 port & try again. + +
@@ -342,48 +279,6 @@
- -
- -
-
🔬 Testing Tools
- -
-
- - -
- -
- -
-
- - -
- - -
- -
-
Test Results
-
-
-
- - -
-
- -
-
- [System] - Waiting for activity... -
diff --git a/opendia-extension/popup.js b/opendia-extension/popup.js index dc36de0..99f2599 100644 --- a/opendia-extension/popup.js +++ b/opendia-extension/popup.js @@ -1,26 +1,29 @@ -// Enhanced Popup with Testing Interface -let logContainer = document.getElementById("log"); +// OpenDia Popup let statusIndicator = document.getElementById("statusIndicator"); let statusText = document.getElementById("statusText"); let toolCount = document.getElementById("toolCount"); let currentPage = document.getElementById("currentPage"); -let resultArea = document.getElementById("results"); -let resultMeta = document.getElementById("result-meta"); -let resultContent = document.getElementById("result-content"); -let dataSizeInfo = document.getElementById("data-size"); -let expandButton = document.getElementById("expand-results"); -let jsonViewer = document.getElementById("json-viewer"); // Get dynamic tool count from background script function updateToolCount() { + const toolsList = [ + "page_analyze", "page_extract_content", "element_click", "element_fill", + "element_get_state", "page_navigate", "page_wait_for", "page_scroll", + "tab_create", "tab_close", "tab_list", "tab_switch", + "get_bookmarks", "add_bookmark", "get_history", "get_selected_text", "get_page_links" + ]; + if (chrome.runtime?.id) { chrome.runtime.sendMessage({ action: "getToolCount" }, (response) => { if (!chrome.runtime.lastError && response?.toolCount) { - toolCount.textContent = response.toolCount; - addLog(`Updated tool count: ${response.toolCount}`, "info"); + toolCount.innerHTML = `${response.toolCount} + Available MCP Tools:\npage_analyze • page_extract_content • element_click • element_fill • element_get_state • page_navigate • page_wait_for • page_scroll • tab_create • tab_close • tab_list • tab_switch • get_bookmarks • add_bookmark • get_history • get_selected_text • get_page_links + `; } else { // Fallback to calculating from background script - toolCount.textContent = "18"; // Expected total based on background script + toolCount.innerHTML = `17 + Available MCP Tools:\npage_analyze • page_extract_content • element_click • element_fill • element_get_state • page_navigate • page_wait_for • page_scroll • tab_create • tab_close • tab_list • tab_switch • get_bookmarks • add_bookmark • get_history • get_selected_text • get_page_links + `; } }); } @@ -32,7 +35,6 @@ function checkStatus() { chrome.runtime.sendMessage({ action: "getStatus" }, (response) => { if (chrome.runtime.lastError) { updateStatus(false); - addLog("Extension background script not responding", "error"); } else { updateStatus(response?.connected || false); } @@ -50,7 +52,6 @@ function checkStatus() { }); } else { updateStatus(false); - addLog("Extension context invalid", "error"); } } @@ -62,10 +63,12 @@ setInterval(checkStatus, 2000); function updateStatus(connected) { if (connected) { statusIndicator.className = "status-indicator connected"; - statusText.textContent = "Connected to MCP server"; + statusText.innerHTML = `Connected to MCP server + Make sure your MCP server is connected. If it's the case, click on Reconnect. If it still don't work, kill your 3000 port & try again.`; } else { statusIndicator.className = "status-indicator disconnected"; - statusText.textContent = "Disconnected from MCP server"; + statusText.innerHTML = `Disconnected from MCP server + Make sure your MCP server is connected. If it's the case, click on Reconnect. If it still don't work, kill your 3000 port & try again.`; } } @@ -73,345 +76,56 @@ function updateStatus(connected) { document.getElementById("reconnectBtn").addEventListener("click", () => { if (chrome.runtime?.id) { chrome.runtime.sendMessage({ action: "reconnect" }, (response) => { - if (chrome.runtime.lastError) { - addLog(chrome.runtime.lastError.message, "error"); - } else { - addLog("Attempting to reconnect...", "info"); + if (!chrome.runtime.lastError) { setTimeout(checkStatus, 1000); } }); } }); -// Test button -document.getElementById("testBtn").addEventListener("click", () => { - if (chrome.runtime?.id) { - chrome.runtime.sendMessage({ action: "test" }, (response) => { - if (chrome.runtime.lastError) { - addLog(chrome.runtime.lastError.message, "error"); - } else { - addLog("Sending test message...", "info"); - } - }); - } -}); - -// Testing Interface Functions -class TestingInterface { - constructor() { - this.setupEventListeners(); - } - - setupEventListeners() { - document.getElementById('test-extract').addEventListener('click', () => this.testExtraction()); - document.getElementById('test-analyze').addEventListener('click', () => this.testAnalysis()); - document.getElementById('highlight-elements').addEventListener('click', () => this.highlightElements()); - expandButton.addEventListener('click', () => this.toggleJsonViewer()); - } - - async testExtraction() { - const contentType = document.getElementById('content-type').value; - addLog(`🔍 Testing content extraction: ${contentType}`, "info"); - - try { - const result = await this.sendToContentScript({ - action: 'extract_content', - data: { content_type: contentType } - }); - - this.displayResults(result, 'Content Extraction'); - addLog(`Extraction completed in ${result.execution_time}ms`, "success"); - } catch (error) { - addLog(`Extraction failed: ${error.message}`, "error"); - } - } - - async testAnalysis() { - const intentHint = document.getElementById('intent-hint').value; - if (!intentHint.trim()) { - addLog('Please enter an intent hint', "error"); - return; - } - - addLog(`🎯 Testing page analysis: ${intentHint}`, "info"); - - try { - const result = await this.sendToContentScript({ - action: 'analyze', - data: { intent_hint: intentHint } - }); - - this.displayResults(result, 'Page Analysis'); - addLog(`Analysis completed in ${result.execution_time}ms`, "success"); - } catch (error) { - addLog(`Analysis failed: ${error.message}`, "error"); - } - } - - async highlightElements() { - const intentHint = document.getElementById('intent-hint').value; - if (!intentHint.trim()) { - addLog('Please enter an intent hint to highlight elements', "error"); - return; - } - - try { - const result = await this.sendToContentScript({ - action: 'analyze', - data: { intent_hint: intentHint } - }); - - if (result.success && result.data.elements?.length > 0) { - // Inject highlighting script - chrome.tabs.query({active: true, currentWindow: true}, (tabs) => { - chrome.scripting.executeScript({ - target: { tabId: tabs[0].id }, - func: this.highlightElementsOnPage, - args: [result.data.elements] - }); - }); - - addLog(`🎯 Highlighted ${result.data.elements.length} elements`, "success"); - } else { - addLog('No elements found to highlight', "error"); - } - } catch (error) { - addLog(`Highlighting failed: ${error.message}`, "error"); - } - } - - highlightElementsOnPage(elements) { - // Remove existing highlights - document.querySelectorAll('.opendia-highlight').forEach(el => { - el.classList.remove('opendia-highlight'); - el.style.removeProperty('outline'); - }); - - // Add new highlights - elements.forEach((elementData, index) => { - try { - const element = document.querySelector(elementData.selector); - if (element) { - element.classList.add('opendia-highlight'); - element.style.outline = `3px solid ${this.getHighlightColor(elementData.confidence)}`; - element.style.outlineOffset = '2px'; - - // Add tooltip - element.title = `OpenDia: ${elementData.name} (${Math.round(elementData.confidence * 100)}%)`; - } - } catch (error) { - console.warn('Failed to highlight element:', elementData.selector, error); - } - }); - - // Auto-remove highlights after 10 seconds - setTimeout(() => { - document.querySelectorAll('.opendia-highlight').forEach(el => { - el.classList.remove('opendia-highlight'); - el.style.removeProperty('outline'); - el.style.removeProperty('outline-offset'); - el.removeAttribute('title'); - }); - }, 10000); - } - - getHighlightColor(confidence) { - if (confidence > 0.8) return '#22c55e'; // Green for high confidence - if (confidence > 0.6) return '#f59e0b'; // Orange for medium confidence - return '#ef4444'; // Red for low confidence - } - - async sendToContentScript(message) { - return new Promise((resolve, reject) => { - chrome.tabs.query({active: true, currentWindow: true}, (tabs) => { - if (tabs[0]) { - chrome.tabs.sendMessage(tabs[0].id, message, (response) => { - if (chrome.runtime.lastError) { - reject(new Error(chrome.runtime.lastError.message)); - } else if (response.success) { - resolve(response); - } else { - reject(new Error(response.error || 'Unknown error')); - } - }); - } else { - reject(new Error('No active tab found')); - } - }); - }); - } - - displayResults(result, testType) { - resultArea.classList.add('show'); - this.lastResult = result; // Store for JSON viewer - - // Display metadata with better formatting - const method = result.data.method || 'N/A'; - const confidence = result.data.confidence ? Math.round(result.data.confidence * 100) + '%' : 'N/A'; - const elementsCount = result.data.elements ? result.data.elements.length : 0; - - resultMeta.innerHTML = ` - ✅ ${testType}
- - 📊 Method: ${method} | - ⏱️ Time: ${result.execution_time}ms | - 🎯 Confidence: ${confidence} - ${elementsCount > 0 ? ` | 🔍 Elements: ${elementsCount}` : ''} - - `; - - // Display enhanced content preview - const preview = this.createEnhancedPreview(result.data, testType); - resultContent.innerHTML = preview; - - // Display size info - this.displayDataSize(result); - - // Store full JSON for viewer - this.updateJsonViewer(result); - } - - createEnhancedPreview(data, testType) { - if (testType === 'Page Analysis' && data.elements?.length > 0) { - return this.createElementsPreview(data.elements); - } else if (testType === 'Content Extraction' && data.content) { - return this.createContentPreview(data.content, data.content_type); - } else { - return `
No relevant data found
`; - } - } - - createElementsPreview(elements) { - const maxElements = 5; - const preview = elements.slice(0, maxElements).map(e => { - const confidenceColor = e.confidence > 0.8 ? '#22c55e' : e.confidence > 0.6 ? '#f59e0b' : '#ef4444'; - return ` -
- ${e.name} -
- Type: ${e.type} | Confidence: ${Math.round(e.confidence * 100)}% | ID: ${e.id} -
-
- ${e.selector} -
-
- `; - }).join(''); - - const remaining = elements.length - maxElements; - const remainingText = remaining > 0 ? `
+ ${remaining} more elements...
` : ''; - - return preview + remainingText; - } - - createContentPreview(content, contentType) { - if (typeof content === 'object') { - if (content.title) { - return ` -
- 📰 ${content.title} -
-
- ${content.word_count ? `📝 Words: ${content.word_count}` : ''} - ${content.reading_time ? ` | ⏱️ Read time: ${content.reading_time}min` : ''} -
-
- ${(content.content || '').substring(0, 200)}${content.content?.length > 200 ? '...' : ''} -
- `; - } else { - return `
${JSON.stringify(content, null, 2).substring(0, 300)}
`; - } - } else { - return `
${content.substring(0, 300)}${content.length > 300 ? '...' : ''}
`; - } - } - - displayDataSize(result) { - const dataSize = result.data_size; - const readableSize = this.formatBytes(dataSize); - const tokenEstimate = Math.round(dataSize / 4); // Rough token estimate - - dataSizeInfo.innerHTML = ` - Data Size: - ${readableSize} - (~${tokenEstimate} tokens) - `; - } - - formatBytes(bytes) { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; - } - - toggleJsonViewer() { - if (jsonViewer.style.display === 'none') { - jsonViewer.style.display = 'block'; - expandButton.textContent = '📄 Hide JSON'; - resultArea.classList.add('test-results-expanded'); - } else { - jsonViewer.style.display = 'none'; - expandButton.textContent = '📄 View Full JSON'; - resultArea.classList.remove('test-results-expanded'); - } - } - - updateJsonViewer(result) { - const formattedJson = this.formatJson(result); - jsonViewer.textContent = formattedJson; - } - - formatJson(obj) { - return JSON.stringify(obj, null, 2); - } -} - -// Initialize testing interface -const testingInterface = new TestingInterface(); - -// Add log entry -function addLog(message, type = "info") { - const entry = document.createElement("div"); - entry.className = "log-entry"; - - const time = document.createElement("span"); - time.className = "log-time"; - time.textContent = `[${new Date().toLocaleTimeString()}]`; - - const content = document.createElement("span"); - content.textContent = message; - - if (type === "error") { - content.style.color = "var(--error-color)"; - } else if (type === "success") { - content.style.color = "var(--success-color)"; - } - - entry.appendChild(time); - entry.appendChild(content); - logContainer.appendChild(entry); - - // Keep only last 20 entries - while (logContainer.children.length > 20) { - logContainer.removeChild(logContainer.firstChild); - } - - logContainer.scrollTop = logContainer.scrollHeight; -} // Listen for updates from background script chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "statusUpdate") { updateStatus(message.connected); - if (message.connected) { - addLog("Connected to MCP server", "success"); - } else { - addLog("Disconnected from MCP server", "error"); - } - } else if (message.type === "log") { - addLog(message.message, message.type || "info"); } -}); \ No newline at end of file +}); + +// Video speed control based on mouse movement +const logoVideo = document.querySelector('.logo video'); +let mouseTimeout; +let lastMouseX = 0; +let lastMouseY = 0; +let mouseSpeed = 0; + +if (logoVideo) { + // Track mouse movement and calculate speed + document.addEventListener('mousemove', (e) => { + const deltaX = e.clientX - lastMouseX; + const deltaY = e.clientY - lastMouseY; + + // Calculate mouse speed (distance moved) + mouseSpeed = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + // Update video playback rate based on mouse speed + // Faster mouse = faster video (clamped between 0.2x and 10x) + // Very sensitive to mouse movement (divided by 15 for more responsiveness) + const playbackRate = Math.min(10, Math.max(0.2, 1 + (mouseSpeed / 15))); + logoVideo.playbackRate = playbackRate; + + lastMouseX = e.clientX; + lastMouseY = e.clientY; + + // Clear existing timeout + clearTimeout(mouseTimeout); + + // Reset to normal speed after mouse stops + mouseTimeout = setTimeout(() => { + logoVideo.playbackRate = 1; + }, 100); + }); + + // Set initial playback rate + logoVideo.playbackRate = 1; +} +