13 Commits

Author SHA1 Message Date
@aaronjmars
8d3c2dc9fc Merge pull request #18 from felixrieseberg/felixr/fix-manifest
fix: manifest.json for dxt
2025-10-21 13:46:10 -04:00
@aaronjmars
eede8cdee5 Update README.md 2025-10-09 17:14:15 +02:00
Felix Rieseberg
8783ae0dc2 fix: manifest.json for dxt 2025-07-28 21:26:16 +02:00
@aaronjmars
9ad89e7248 Update README.md 2025-07-27 16:36:51 +02:00
@aaronjmars
9dcbd230c5 Update README.md 2025-07-20 23:51:26 +02:00
Aaron Elijah Mars
daa4b55ee5 Update to version 1.1.0 with tab_list timeout fixes and enhanced documentation
- Fixed tab_list timeout issue on Chrome by increasing content script timeout from 1s to 3s
- Updated all package.json and manifest files to version 1.1.0
- Set check_content_script default to false to avoid timeouts
- Enhanced DXT build script with comprehensive installation guide and GitHub repo links
- Fixed OpenDia logo path in README.md
- Updated tool examples and documentation to reflect timeout improvements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 23:43:30 +02:00
Aaron Elijah Mars
0b09201160 Update to version 1.0.6 with Firefox support and improved extension management
- Add Firefox extension support with Manifest V2
- Update all version numbers to 1.0.6
- Build Chrome and Firefox extension packages
- Clean up extension directory structure
- Update README with Firefox installation instructions
- Update server messages to mention Chrome/Firefox support
- Add .gitignore for extension directory
- Create release packages for both browsers

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 22:57:39 +02:00
Aaron Elijah Mars
99d31b15c8 Update opendia.dxt to version 1.0.5
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 22:11:29 +02:00
Aaron Elijah Mars
a83df06513 Bump version to 1.0.5 and update installation instructions
- Update version to 1.0.5 in all package files and build script
- Add double-click .dxt installation as recommended option in README
- Keep npx option as alternative installation method

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 22:11:00 +02:00
Aaron Elijah Mars
05559b8b1d styling features 2025-07-15 20:15:02 +02:00
Aaron Elijah Mars
ca1a88776d bulk tabs (open + query + act) 2025-07-15 19:52:39 +02:00
Aaron Elijah Mars
14be38dd16 safety mode 2025-07-15 17:43:08 +02:00
Aaron Elijah Mars
aa78576ad7 autokill port + prompts 2025-07-15 17:33:03 +02:00
27 changed files with 9183 additions and 1283 deletions

View File

@@ -1,7 +1,9 @@
# OpenDia <img src="opendia-extension/icon-128.png" alt="OpenDia" width="32" height="32">
# OpenDia <img src="opendia-extension/icons/icon-128.png" alt="OpenDia" width="32" height="32">
**The open alternative to Dia**
Connect your browser to AI models. No browser switching needed—works seamlessly with any Chromium browser including Chrome & Arc.
**The open alternative to Dia / Perplexity Comet**
Connect your browser to AI models.
No browser switching needed—works seamlessly with Chrome, Firefox, and any Chromium browser. Private, local-first & MCP focused.
If you are not technical / never used MCPs before, we recommend using **[Perplexity Comet](https://pplx.ai/leosimon)**.
[![npm version](https://img.shields.io/npm/v/opendia)](https://www.npmjs.com/package/opendia)
[![GitHub release](https://img.shields.io/github/release/aaronjmars/opendia.svg)](https://github.com/aaronjmars/opendia/releases/latest)
@@ -31,15 +33,16 @@ OpenDia lets AI models control your browser automatically. **The key advantage?
## 🌐 Browser Support
Works with **any Chromium-based browser**:
Works with **Chrome, Firefox, and any Chromium-based browser**:
-**Google Chrome**
-**Arc**
-**Arc**
-**Mozilla Firefox**
-**Microsoft Edge**
-**Brave**
-**Opera**
-**Any Chromium based browser**
Perfect for **Cursor users** who want to automate their local testing and development workflows!
Also perfect for **Cursor users** who want to automate their local testing and development workflows!
## 🎬 What You Can Do
@@ -69,16 +72,42 @@ Perfect for **Cursor users** who want to automate their local testing and develo
- **"Monitor this webpage and notify me when the content changes"**
- **"Automatically bookmark interesting articles I'm reading"**
### 🎨 Visual Customization & Fun
- **"Apply a cyberpunk theme to this documentation site to make it more engaging"**
- **"Make this page dark mode with green text for late-night reading"**
- **"Add rainbow party effects to celebrate finishing this project"**
- **"Transform this boring form with a retro 80s theme while I fill it out"**
- **"Use high contrast styling so I can read this better"**
## ⚡ Quick Start
### 1. Install the Browser Extension
1. Download from [releases](https://github.com/aaronjmars/opendia/releases)
2. Go to `chrome://extensions/` (or your browser's extension page)
3. Enable "Developer mode"
4. Click "Load unpacked" and select the extension folder
**For Chrome/Chromium browsers:**
1. Download `opendia-chrome-1.1.0.zip` from [releases](https://github.com/aaronjmars/opendia/releases)
2. Extract the zip file to a folder
3. Go to `chrome://extensions/` (or your browser's extension page)
4. Enable "Developer mode"
5. Click "Load unpacked" and select the extracted folder
**For Firefox:**
1. Download `opendia-firefox-1.1.0.zip` from [releases](https://github.com/aaronjmars/opendia/releases)
2. Extract the zip file to a folder
3. Go to `about:debugging#/runtime/this-firefox`
4. Click "Load Temporary Add-on..."
5. Select the `manifest.json` file from the extracted folder
> **Note**: Firefox extensions are loaded as temporary add-ons and will be removed when Firefox restarts. This is a Firefox limitation for unsigned extensions.
### 2. Connect to Your AI
**For Claude Desktop**, add to your configuration:
**Option 1: Double-click Installation (Recommended)**
1. Download the `opendia.dxt` file from [releases](https://github.com/aaronjmars/opendia/releases)
2. Double-click the `.dxt` file to install automatically
3. The MCP will be added to your Claude Desktop configuration
**Option 2: Manual Configuration**
Add to your Claude Desktop configuration:
```json
{
"mcpServers": {
@@ -109,7 +138,7 @@ npx opendia --port=6000 # Uses 6000 (WebSocket) + 6001 (HTTP)
npx opendia --ws-port=5555 --http-port=5556 # Specify individually
# Handle port conflicts
npx opendia --kill-existing # Safely terminate existing OpenDia processes
# Note: Existing OpenDia processes are automatically terminated on startup
```
### Auto-Tunnel Mode
@@ -152,7 +181,7 @@ ngrok config add-authtoken YOUR_TOKEN_HERE
## 🛠️ Capabilities
OpenDia gives AI models **17 powerful browser tools**:
OpenDia gives AI models **18 powerful browser tools**:
### 🎯 Smart Page Understanding
- **Analyze any webpage** - AI automatically finds buttons, forms, and interactive elements
@@ -180,6 +209,13 @@ OpenDia gives AI models **17 powerful browser tools**:
- **Natural interactions** - Mimics human behavior to avoid triggering security measures
- **Reliable automation** - Works consistently even on sites that block typical automation tools
### 🎨 Page Styling & Customization
- **Transform any website** - Apply fun themes, custom colors, and visual effects
- **Preset themes** - Dark hacker, retro 80s, rainbow party, minimalist zen, and more
- **AI mood styling** - Describe a mood and get matching visual design
- **Interactive effects** - Matrix rain, floating particles, neon glow, and cursor trails
- **Accessibility themes** - High contrast and readable designs for better visibility
## 💬 Example Prompts to Try
Once everything is set up, try asking your AI:
@@ -202,6 +238,15 @@ Once everything is set up, try asking your AI:
**Personal Assistant:**
> *"Find that GitHub repo I was looking at yesterday about React components and bookmark it for later"*
**Page Styling & Fun:**
> *"Apply a dark hacker theme to this page to make it look more interesting"*
> *"Make this boring documentation page feel like a cozy coffee shop"*
> *"Add some matrix rain effects to this page for 30 seconds for a cool screenshot"*
> *"Transform this page with a high contrast theme for better readability"*
## 🏗️ How It Works
```mermaid
@@ -242,7 +287,8 @@ npm install
npm start
# Load extension in your browser
# Go to chrome://extensions/ → Developer mode → Load unpacked: ./opendia-extension
# Chrome: Go to chrome://extensions/ → Developer mode → Load unpacked: ./opendia-extension/dist/chrome
# Firefox: Go to about:debugging#/runtime/this-firefox → Load Temporary Add-on → ./opendia-extension/dist/firefox/manifest.json
# Extension will auto-connect to server on localhost:5555
```

401
build-dxt.sh Executable file
View File

@@ -0,0 +1,401 @@
#!/bin/bash
# OpenDia DXT Build Script - Fixed Version
# Run this from the OpenDia project root directory
set -e # Exit on any error
echo "🚀 Building OpenDia DXT package..."
# Check if we're in the right directory
if [ ! -f "opendia-mcp/package.json" ]; then
echo "❌ Error: Please run this script from the OpenDia project root directory"
echo " Expected to find: opendia-mcp/package.json"
echo " Current directory: $(pwd)"
exit 1
fi
if [ ! -d "opendia-extension" ]; then
echo "❌ Error: opendia-extension directory not found"
exit 1
fi
# Clean and create dist directory
echo "🧹 Cleaning previous build..."
rm -rf dist
mkdir -p dist/opendia-dxt
echo "📦 Setting up package..."
# Copy server files
cp opendia-mcp/server.js dist/opendia-dxt/
# Create optimized package.json for DXT
cat > dist/opendia-dxt/package.json << 'EOF'
{
"name": "opendia",
"version": "1.1.0",
"description": "🎯 OpenDia - The open alternative to Dia. Connect your browser to AI models with anti-detection bypass for Twitter/X, LinkedIn, Facebook",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"keywords": [
"mcp",
"browser",
"automation",
"ai",
"claude",
"chrome",
"firefox",
"extension",
"twitter",
"linkedin",
"facebook",
"anti-detection",
"dxt"
],
"author": "Aaron Elijah Mars <aaronjmars@proton.me>",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.21.2",
"ws": "^8.18.0"
},
"engines": {
"node": ">=16.0.0"
}
}
EOF
# Install dependencies
echo "⬇️ Installing dependencies..."
cd dist/opendia-dxt
npm install --production --silent
cd ../..
# Copy browser extension
echo "🌐 Copying browser extension..."
cp -r opendia-extension dist/opendia-dxt/extension
# Copy logo/icon files for DXT - try multiple sources
echo "🎨 Copying logo files..."
LOGO_COPIED=false
# Try different icon files from the extension
for icon_file in "icon-128.png" "icon-48.png" "icon-32.png" "icon-16.png" "icon.png"; do
if [ -f "opendia-extension/$icon_file" ]; then
cp "opendia-extension/$icon_file" dist/opendia-dxt/icon.png
echo "✅ Logo copied from extension/$icon_file"
LOGO_COPIED=true
break
fi
done
# If no extension icon found, check root directory
if [ "$LOGO_COPIED" = false ]; then
for icon_file in "icon.png" "logo.png" "opendia.png"; do
if [ -f "$icon_file" ]; then
cp "$icon_file" dist/opendia-dxt/icon.png
echo "✅ Logo copied from $icon_file"
LOGO_COPIED=true
break
fi
done
fi
# Create a simple placeholder if no icon found
if [ "$LOGO_COPIED" = false ]; then
echo "⚠️ No logo file found, you may need to add icon.png manually to dist/opendia-dxt/"
fi
# Create DXT manifest - CORRECT FORMAT BASED ON WORKING EXAMPLES
echo "📋 Creating DXT manifest..."
cat > dist/opendia-dxt/manifest.json << 'EOF'
{
"dxt_version": "0.1",
"name": "opendia",
"display_name": "OpenDia - Browser Automation",
"version": "1.1.0",
"description": "🎯 OpenDia - The open alternative to Dia. Connect your browser to AI models with anti-detection bypass for Twitter/X, LinkedIn, Facebook + universal automation",
"author": {
"name": "Aaron Elijah Mars",
"email": "aaronjmars@proton.me",
"url": "https://github.com/aaronjmars/opendia"
},
"homepage": "https://github.com/aaronjmars/opendia",
"license": "MIT",
"keywords": ["browser", "automation", "mcp", "ai", "claude", "chrome", "firefox", "extension", "twitter", "linkedin", "facebook", "anti-detection"],
"icon": "icon.png",
"server": {
"type": "node",
"entry_point": "server.js",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/server.js"],
"env": {
"NODE_ENV": "production",
"WS_PORT": "${user_config.ws_port}",
"HTTP_PORT": "${user_config.http_port}",
"ENABLE_TUNNEL": "${user_config.enable_tunnel}",
"SAFETY_MODE": "${user_config.safety_mode}"
}
}
},
"user_config": {
"ws_port": {
"type": "number",
"title": "WebSocket Port",
"description": "Port for Chrome/Firefox extension connection",
"default": 5555,
"min": 1024,
"max": 65535
},
"http_port": {
"type": "number",
"title": "HTTP Port",
"description": "Port for HTTP/SSE server",
"default": 5556,
"min": 1024,
"max": 65535
},
"enable_tunnel": {
"type": "boolean",
"title": "Auto-Tunnel",
"description": "Automatically create ngrok tunnel for online AI access (requires ngrok)",
"default": false
},
"safety_mode": {
"type": "boolean",
"title": "Safety Mode",
"description": "Block write/edit tools (element_click, element_fill) by default",
"default": false
}
},
"tools": [
{
"name": "page_analyze",
"description": "🔍 Analyze any tab without switching! Two-phase intelligent page analysis"
},
{
"name": "page_extract_content",
"description": "📄 Extract content from any tab without switching!"
},
{
"name": "element_click",
"description": "🖱️ Click elements with anti-detection bypass for social platforms"
},
{
"name": "element_fill",
"description": "✏️ Fill forms with anti-detection bypass for Twitter/X, LinkedIn, Facebook"
},
{
"name": "page_navigate",
"description": "🧭 Navigate to URLs with wait conditions"
},
{
"name": "page_wait_for",
"description": "⏳ Wait for elements or conditions"
},
{
"name": "tab_create",
"description": "📱 Create single or multiple tabs with batch support"
},
{
"name": "tab_close",
"description": "❌ Close specific tab(s) by ID"
},
{
"name": "tab_list",
"description": "📋 Get list of all open tabs with IDs"
},
{
"name": "tab_switch",
"description": "🔄 Switch to specific tab by ID"
},
{
"name": "element_get_state",
"description": "🔍 Get detailed element state information"
},
{
"name": "get_bookmarks",
"description": "📚 Get all bookmarks or search for specific ones"
},
{
"name": "add_bookmark",
"description": " Add new bookmark"
},
{
"name": "get_history",
"description": "🕒 Search browser history with comprehensive filters"
},
{
"name": "get_selected_text",
"description": "📝 Get selected text from any tab"
},
{
"name": "page_scroll",
"description": "📜 Scroll any tab without switching"
},
{
"name": "get_page_links",
"description": "🔗 Get all hyperlinks with smart filtering"
},
{
"name": "page_style",
"description": "🎨 Transform page appearance with themes and effects"
}
]
}
EOF
# Validate JSON syntax
echo "🔍 Validating manifest.json..."
if ! python3 -m json.tool dist/opendia-dxt/manifest.json > /dev/null 2>&1; then
echo "❌ Error: Invalid JSON in manifest.json"
exit 1
fi
echo "✅ Manifest JSON is valid"
# Copy documentation
echo "📝 Adding documentation..."
cp README.md dist/opendia-dxt/ 2>/dev/null || echo "⚠️ README.md not found, skipping"
cp LICENSE dist/opendia-dxt/ 2>/dev/null || echo "⚠️ LICENSE not found, skipping"
# Create extension installation guide
cat > dist/opendia-dxt/EXTENSION_INSTALL.md << 'EOF'
# OpenDia Browser Extension Installation
**🔗 Official Repository:** https://github.com/aaronjmars/opendia
## Complete Installation Guide
### Step 1: Install the DXT Package (Already Done!)
✅ You've successfully installed the OpenDia DXT package in Claude Desktop
### Step 2: Install Browser Extension
**📦 Get Latest Extension:**
Download the latest extension from: https://github.com/aaronjmars/opendia/releases
**Or use the included extension in this DXT package:**
#### For Chrome/Chromium Browsers
1. **Enable Developer Mode**
- Go to `chrome://extensions/`
- Toggle "Developer mode" in the top right
2. **Install Extension**
- Click "Load unpacked"
- Select the `extension/` folder from this DXT package
- Extension should appear in your extensions list with OpenDia icon
#### For Firefox
1. **Load Temporary Add-on**
- Go to `about:debugging#/runtime/this-firefox`
- Click "Load Temporary Add-on..."
- Select the `manifest-firefox.json` file from the `extension/` folder
> **Firefox Note**: Extensions are loaded as temporary add-ons and will be removed when Firefox restarts. For permanent installation, use the signed extension from GitHub releases.
### Step 3: Verify Everything Works
1. **Check Extension Status**
- Click the OpenDia extension icon in your browser
- Should show "Connected to MCP server"
- Green status indicator means ready to use
2. **Test in Claude Desktop**
- Ask Claude: "List my open browser tabs"
- Should return your current tabs if working correctly
## Supported Browsers
- ✅ **Mozilla Firefox** (Manifest V2)
- ✅ **Google Chrome** (Manifest V3)
- ✅ **Arc Browser, Microsoft Edge, Brave Browser, Opera**
- ✅ **Any Chromium-based browser**
## Key Features
🎯 **Anti-detection bypass** for Twitter/X, LinkedIn, Facebook
📱 **Smart automation** and intelligent page analysis
🔧 **Form filling** with enhanced compatibility
📊 **Multi-tab workflows** and background operations
🎨 **Page styling** and visual customization
🔒 **Privacy-first** - everything runs locally
## Getting Help
- **📖 Full Documentation:** https://github.com/aaronjmars/opendia
- **🐛 Report Issues:** https://github.com/aaronjmars/opendia/issues
- **💬 Discussions:** https://github.com/aaronjmars/opendia/discussions
## What's Next?
Try these example prompts in Claude Desktop:
- "List my open browser tabs"
- "Analyze the content of my current tab"
- "Apply a dark theme to this webpage"
- "Extract the main article text from this page"
**🚀 Ready to supercharge your browser with AI!**
EOF
# Verify structure before zipping
echo "🔍 Verifying DXT structure..."
echo "📋 Files in DXT directory:"
ls -la dist/opendia-dxt/
# Check if icon exists and show its details
if [ -f "dist/opendia-dxt/icon.png" ]; then
echo "✅ Icon file found:"
file dist/opendia-dxt/icon.png
ls -lh dist/opendia-dxt/icon.png
else
echo "❌ Icon file missing!"
fi
# Verify manifest.json exists and is at root level
if [ ! -f "dist/opendia-dxt/manifest.json" ]; then
echo "❌ Error: manifest.json not found!"
exit 1
fi
echo "✅ Structure verified"
# Create the DXT archive - CRITICAL: ZIP from inside the directory
echo "🗜️ Creating DXT archive..."
cd dist/opendia-dxt
zip -r ../opendia.dxt . -q
cd ../..
# Verify the DXT file structure
echo "🔍 Verifying DXT file contents..."
if ! unzip -l dist/opendia.dxt | grep -q "manifest.json"; then
echo "❌ Error: manifest.json not found in DXT file!"
echo "DXT contents:"
unzip -l dist/opendia.dxt
exit 1
fi
# Get file size
DXT_SIZE=$(du -h dist/opendia.dxt | cut -f1)
echo ""
echo "✅ DXT package created successfully!"
echo "📦 File: dist/opendia.dxt"
echo "💾 Size: $DXT_SIZE"
echo ""
echo "📋 DXT Contents:"
unzip -l dist/opendia.dxt | head -10
echo ""
echo "🚀 Installation:"
echo "1. Double-click the .dxt file"
echo "2. Or: Claude Desktop Settings → Extensions → Install Extension"
echo "3. Install Chrome/Firefox extension from extension/ folder"
echo ""
echo "🎯 Features ready: Anti-detection bypass + universal automation"

2
opendia-extension/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules/
dist/

312
opendia-extension/README.md Normal file
View File

@@ -0,0 +1,312 @@
# OpenDia Cross-Browser Extension
A dual-manifest browser extension supporting both Chrome MV3 and Firefox MV2 for comprehensive browser automation through the Model Context Protocol (MCP).
## 🌐 Browser Support
| Browser | Manifest | Background | Connection | Store |
|---------|----------|------------|------------|-------|
| Chrome | V3 | Service Worker | Temporary | Chrome Web Store |
| Firefox | V2 | Background Page | Persistent | Firefox Add-ons |
| Edge | V3 | Service Worker | Temporary | Microsoft Store |
| Safari | - | - | - | Coming Soon |
## 🚀 Quick Start
### Development Setup
```bash
# Install dependencies
npm install
# Build for all browsers
npm run build
# Build for specific browser
npm run build:chrome
npm run build:firefox
# Create distribution packages
npm run package:chrome
npm run package:firefox
# Test builds
node test-extension.js
```
### Installation
#### Chrome/Edge/Brave
1. Build the extension: `npm run build:chrome`
2. Open `chrome://extensions/` (or equivalent)
3. Enable "Developer mode"
4. Click "Load unpacked" and select `dist/chrome`
#### Firefox
1. Build the extension: `npm run build:firefox`
2. Open `about:debugging#/runtime/this-firefox`
3. Click "Load Temporary Add-on"
4. Select any file in `dist/firefox` directory
## 🏗️ Architecture
### Dual-Manifest Strategy
OpenDia uses a dual-manifest approach to maximize browser compatibility:
- **Chrome MV3**: Required for Chrome Web Store, uses service workers
- **Firefox MV2**: Enhanced capabilities, persistent background pages
### Connection Management
```javascript
// Chrome MV3: Temporary connections
class ServiceWorkerManager {
async ensureConnection() {
// Create fresh connection for each operation
await this.createTemporaryConnection();
}
}
// Firefox MV2: Persistent connections
class BackgroundPageManager {
constructor() {
this.persistentSocket = null;
this.setupPersistentConnection();
}
}
```
### Cross-Browser Compatibility
The extension uses WebExtension polyfill for consistent API usage:
```javascript
// Polyfill setup
if (typeof browser === 'undefined' && typeof chrome !== 'undefined') {
globalThis.browser = chrome;
}
// Unified API usage
const tabs = await browser.tabs.query({active: true, currentWindow: true});
```
## 🔧 Build System
### Build Configuration
The build system creates browser-specific packages:
```javascript
// build.js
async function buildForBrowser(browser) {
// Copy common files
await fs.copy('src', path.join(buildDir, 'src'));
// Copy browser-specific manifest
await fs.copy(`manifest-${browser}.json`, path.join(buildDir, 'manifest.json'));
// Copy WebExtension polyfill
await fs.copy('node_modules/webextension-polyfill/dist/browser-polyfill.min.js',
path.join(buildDir, 'src/polyfill/browser-polyfill.min.js'));
}
```
### Build Validation
```bash
# Validate all builds
npm run build && node build.js validate
# Check specific browser
node build.js validate chrome
node build.js validate firefox
```
## 🧪 Testing
### Automated Testing
```bash
# Run comprehensive tests
node test-extension.js
# Test specific components
node test-extension.js --manifest
node test-extension.js --background
node test-extension.js --content
```
### Manual Testing
1. **Connection Test**: Extension popup should show "Connected to MCP server"
2. **Background Tab Test**: Use `tab_list` with `check_content_script: true`
3. **Cross-Browser Test**: Same functionality on both Chrome and Firefox
## 📁 Directory Structure
```
opendia-extension/
├── src/
│ ├── background/
│ │ └── background.js # Cross-browser background script
│ ├── content/
│ │ └── content.js # Content script with polyfill
│ ├── popup/
│ │ ├── popup.html # Extension popup
│ │ └── popup.js # Popup logic with browser APIs
│ └── polyfill/
│ └── browser-polyfill.min.js # WebExtension polyfill
├── icons/ # Extension icons
├── dist/ # Build output
│ ├── chrome/ # Chrome MV3 build
│ ├── firefox/ # Firefox MV2 build
│ ├── opendia-chrome.zip # Chrome package
│ └── opendia-firefox.zip # Firefox package
├── manifest-chrome.json # Chrome MV3 manifest
├── manifest-firefox.json # Firefox MV2 manifest
├── build.js # Build system
├── test-extension.js # Test suite
└── package.json # Dependencies and scripts
```
## 🔗 Integration
### MCP Server Connection
The extension automatically discovers and connects to the MCP server:
```javascript
// Port discovery
const commonPorts = [5556, 5557, 5558, 3001, 6001, 6002, 6003];
const response = await fetch(`http://localhost:${port}/ports`);
const portInfo = await response.json();
```
### Background Tab Support
All tools support background tab targeting:
```javascript
// Target specific tab
await browser.tabs.sendMessage(tabId, {
action: 'page_analyze',
data: { intent_hint: 'login', tab_id: 12345 }
});
```
## 🛠️ Development
### Adding New Features
1. **Cross-Browser First**: Use `browser` API throughout
2. **Connection Aware**: Handle both temporary and persistent connections
3. **Test Both Browsers**: Validate on Chrome and Firefox
4. **Update Both Manifests**: Ensure compatibility
### Browser-Specific Handling
```javascript
// Detect browser environment
const browserInfo = {
isFirefox: typeof browser !== 'undefined' && browser.runtime.getManifest().applications?.gecko,
isChrome: typeof chrome !== 'undefined' && !browser.runtime.getManifest().applications?.gecko,
isServiceWorker: typeof importScripts === 'function',
manifestVersion: browser.runtime.getManifest().manifest_version
};
// Handle differences
if (browserInfo.isServiceWorker) {
// Chrome MV3 service worker behavior
} else {
// Firefox MV2 background page behavior
}
```
### API Compatibility
| Feature | Chrome MV3 | Firefox MV2 | Implementation |
|---------|------------|-------------|----------------|
| Background | Service Worker | Background Page | Connection Manager |
| Script Injection | `browser.scripting` | `browser.tabs.executeScript` | Feature detection |
| Persistent State | ❌ | ✅ | Browser-specific storage |
| WebRequest Blocking | Limited | Full | Firefox advantage |
| Store Distribution | Required | Optional | Both supported |
## 🚀 Distribution
### Chrome Web Store
```bash
# Build and package
npm run package:chrome
# Upload dist/opendia-chrome.zip to Chrome Web Store
```
### Firefox Add-ons (AMO)
```bash
# Build and package
npm run package:firefox
# Upload dist/opendia-firefox.zip to addons.mozilla.org
```
### GitHub Releases
```bash
# Create both packages
npm run package:chrome
npm run package:firefox
# Upload both files to GitHub releases
```
## 🤝 Contributing
1. **Test Both Browsers**: Always test Chrome and Firefox
2. **Use Browser APIs**: Avoid `chrome.*` direct usage
3. **Update Both Manifests**: Keep manifests in sync
4. **Validate Builds**: Run test suite before committing
## 📚 Resources
- [WebExtension API Documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions)
- [Chrome Extension MV3 Guide](https://developer.chrome.com/docs/extensions/mv3/)
- [Firefox Extension Development](https://extensionworkshop.com/)
- [WebExtension Polyfill](https://github.com/mozilla/webextension-polyfill)
## 🔧 Troubleshooting
### Common Issues
1. **Connection Fails**: Check MCP server is running (`npm start` in `opendia-mcp/`)
2. **Chrome Service Worker**: Extensions may need manual restart in `chrome://extensions`
3. **Firefox Temporary**: Extension reloads required after Firefox restart
4. **Build Errors**: Ensure all dependencies installed (`npm install`)
### Debug Commands
```bash
# Check server status
curl http://localhost:5556/ping
# Validate builds
node build.js validate
# Test extension compatibility
node test-extension.js
# Check extension logs
# Chrome: chrome://extensions -> OpenDia -> service worker
# Firefox: about:debugging -> OpenDia -> Inspect
```
## 🎯 Future Enhancements
- [ ] Safari extension support
- [ ] Edge-specific optimizations
- [ ] WebExtension Manifest V3 migration for Firefox
- [ ] Enhanced anti-detection features
- [ ] Performance optimizations for service workers

File diff suppressed because it is too large Load Diff

193
opendia-extension/build.js Normal file
View File

@@ -0,0 +1,193 @@
const fs = require('fs-extra');
const path = require('path');
async function buildForBrowser(browser) {
const buildDir = `dist/${browser}`;
console.log(`🔧 Building ${browser} extension...`);
// Clean and create build directory
await fs.remove(buildDir);
await fs.ensureDir(buildDir);
// Copy common files
await fs.copy('src', path.join(buildDir, 'src'));
await fs.copy('icons', path.join(buildDir, 'icons'));
// Copy logo files for animated popup
if (await fs.pathExists('logo.mp4')) {
await fs.copy('logo.mp4', path.join(buildDir, 'logo.mp4'));
}
if (await fs.pathExists('logo.webm')) {
await fs.copy('logo.webm', path.join(buildDir, 'logo.webm'));
}
// Copy browser-specific manifest
await fs.copy(
`manifest-${browser}.json`,
path.join(buildDir, 'manifest.json')
);
// Copy polyfill
await fs.copy(
'node_modules/webextension-polyfill/dist/browser-polyfill.min.js',
path.join(buildDir, 'src/polyfill/browser-polyfill.min.js')
);
// Browser-specific post-processing
if (browser === 'chrome') {
console.log('📦 Chrome MV3: Service worker mode enabled');
// No additional processing needed for Chrome
} else if (browser === 'firefox') {
console.log('🦊 Firefox MV2: Background page mode enabled');
// No additional processing needed for Firefox
}
console.log(`${browser} extension built successfully in ${buildDir}`);
}
async function buildAll() {
console.log('🚀 Building extensions for all browsers...');
try {
await buildForBrowser('chrome');
await buildForBrowser('firefox');
console.log('🎉 All extensions built successfully!');
console.log('');
console.log('📁 Build outputs:');
console.log(' Chrome MV3: dist/chrome/');
console.log(' Firefox MV2: dist/firefox/');
console.log('');
console.log('🧪 Testing instructions:');
console.log(' Chrome: Load dist/chrome in chrome://extensions (Developer mode)');
console.log(' Firefox: Load dist/firefox in about:debugging#/runtime/this-firefox');
} catch (error) {
console.error('❌ Build failed:', error);
process.exit(1);
}
}
async function createPackages() {
console.log('📦 Creating distribution packages...');
const { execSync } = require('child_process');
try {
// Create Chrome package
console.log('Creating Chrome package...');
execSync('cd dist/chrome && zip -r ../opendia-chrome.zip .', { stdio: 'inherit' });
// Create Firefox package (using web-ext if available)
console.log('Creating Firefox package...');
try {
execSync('cd dist/firefox && web-ext build --overwrite-dest', { stdio: 'inherit' });
console.log('✅ Firefox package created with web-ext');
} catch (e) {
// Fallback to zip if web-ext is not available
console.log('⚠️ web-ext not available, using zip fallback');
execSync('cd dist/firefox && zip -r ../opendia-firefox.zip .', { stdio: 'inherit' });
}
console.log('📦 Distribution packages created:');
console.log(' Chrome: dist/opendia-chrome.zip');
console.log(' Firefox: dist/opendia-firefox.zip (or .xpi)');
} catch (error) {
console.error('❌ Package creation failed:', error);
process.exit(1);
}
}
async function validateBuild(browser) {
const buildDir = `dist/${browser}`;
const manifestPath = path.join(buildDir, 'manifest.json');
console.log(`🔍 Validating ${browser} build...`);
try {
// Check manifest exists and is valid JSON
const manifest = await fs.readJson(manifestPath);
// Check required files exist
const requiredFiles = [
'src/background/background.js',
'src/content/content.js',
'src/popup/popup.html',
'src/popup/popup.js',
'src/polyfill/browser-polyfill.min.js'
];
for (const file of requiredFiles) {
const filePath = path.join(buildDir, file);
if (!await fs.pathExists(filePath)) {
throw new Error(`Missing required file: ${file}`);
}
}
// Browser-specific validation
if (browser === 'chrome') {
if (manifest.manifest_version !== 3) {
throw new Error('Chrome build must use manifest version 3');
}
if (!manifest.background?.service_worker) {
throw new Error('Chrome build must specify service_worker in background');
}
} else if (browser === 'firefox') {
if (manifest.manifest_version !== 2) {
throw new Error('Firefox build must use manifest version 2');
}
if (!manifest.background?.scripts) {
throw new Error('Firefox build must specify scripts in background');
}
}
console.log(`${browser} build validation passed`);
return true;
} catch (error) {
console.error(`${browser} build validation failed:`, error.message);
return false;
}
}
async function validateAllBuilds() {
console.log('🔍 Validating all builds...');
const chromeValid = await validateBuild('chrome');
const firefoxValid = await validateBuild('firefox');
if (chromeValid && firefoxValid) {
console.log('✅ All builds validated successfully');
return true;
} else {
console.error('❌ Build validation failed');
return false;
}
}
// CLI usage
if (require.main === module) {
const args = process.argv.slice(2);
const command = args[0];
switch (command) {
case 'chrome':
buildForBrowser('chrome');
break;
case 'firefox':
buildForBrowser('firefox');
break;
case 'validate':
validateAllBuilds();
break;
case 'package':
buildAll().then(() => createPackages());
break;
default:
buildAll();
}
}
module.exports = { buildForBrowser, buildAll, createPackages, validateBuild, validateAllBuilds };

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,46 @@
{
"manifest_version": 3,
"name": "OpenDia",
"version": "1.1.0",
"description": "Connect your browser to AI models",
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
},
"permissions": [
"tabs",
"activeTab",
"storage",
"scripting",
"webNavigation",
"notifications",
"bookmarks",
"history"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "src/background/background.js",
"type": "module"
},
"action": {
"default_popup": "src/popup/popup.html",
"default_title": "OpenDia"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/polyfill/browser-polyfill.min.js", "src/content/content.js"],
"run_at": "document_idle"
}
],
"web_accessible_resources": [
{
"resources": ["src/polyfill/browser-polyfill.min.js"],
"matches": ["<all_urls>"]
}
]
}

View File

@@ -0,0 +1,51 @@
{
"manifest_version": 2,
"name": "OpenDia",
"version": "1.1.0",
"description": "Connect your browser to AI models",
"applications": {
"gecko": {
"id": "opendia@aaronjmars.com",
"strict_min_version": "109.0"
}
},
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
},
"permissions": [
"tabs",
"activeTab",
"storage",
"webNavigation",
"notifications",
"bookmarks",
"history",
"webRequest",
"webRequestBlocking",
"<all_urls>"
],
"background": {
"scripts": [
"src/polyfill/browser-polyfill.min.js",
"src/background/background.js"
],
"persistent": false
},
"browser_action": {
"default_popup": "src/popup/popup.html",
"default_title": "OpenDia"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/polyfill/browser-polyfill.min.js", "src/content/content.js"],
"run_at": "document_idle"
}
],
"web_accessible_resources": [
"src/polyfill/browser-polyfill.min.js"
]
}

View File

@@ -1,8 +1,8 @@
{
"manifest_version": 3,
"name": "OpenDia",
"version": "1.0.4",
"description": "Browser automation through Model Context Protocol",
"version": "1.1.0",
"description": "Connect your browser to AI models",
"icons": {
"16": "icon-16.png",
"32": "icon-32.png",

4319
opendia-extension/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
{
"name": "opendia-extension",
"version": "1.1.0",
"description": "Connect your browser to AI models",
"scripts": {
"build": "node build.js",
"build:chrome": "node build.js chrome",
"build:firefox": "node build.js firefox",
"dev:chrome": "npm run build:chrome && echo 'Load dist/chrome in Chrome'",
"dev:firefox": "npm run build:firefox && web-ext run --source-dir=dist/firefox",
"package:chrome": "npm run build:chrome && cd dist/chrome && zip -r ../opendia-chrome.zip .",
"package:firefox": "npm run build:firefox && cd dist/firefox && web-ext build --overwrite-dest"
},
"keywords": [
"webextension",
"mcp",
"browser-automation"
],
"author": "Aaron J Mars",
"license": "MIT",
"dependencies": {
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"fs-extra": "^11.3.0",
"web-ext": "^8.8.0"
}
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,15 @@
// Enhanced Browser Automation Content Script with Anti-Detection
// Import WebExtension polyfill for cross-browser compatibility
if (typeof browser === 'undefined' && typeof chrome !== 'undefined') {
globalThis.browser = chrome;
}
// Prevent multiple injections - especially important for Firefox
if (typeof window.OpenDiaContentScriptLoaded !== 'undefined') {
console.log("OpenDia content script already loaded, skipping re-injection");
} else {
window.OpenDiaContentScriptLoaded = true;
console.log("OpenDia enhanced content script loaded");
// Enhanced Pattern Database with Twitter-First Priority
@@ -228,7 +239,7 @@ class BrowserAutomation {
}
setupMessageListener() {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
this.handleMessage(message)
.then(sendResponse)
.catch((error) => {
@@ -283,6 +294,13 @@ class BrowserAutomation {
case "page_scroll":
result = await this.scrollPage(data);
break;
case "page_style":
result = await this.handlePageStyle(data);
break;
case "ping":
// Health check for background tab content script readiness
result = { status: "ready", timestamp: Date.now(), url: window.location.href };
break;
default:
throw new Error(`Unknown action: ${action}`);
}
@@ -2503,7 +2521,377 @@ class BrowserAutomation {
};
}
}
// 🎨 Page Styling System
async handlePageStyle(data) {
const { mode, theme, background, text_color, font, font_size, mood, intensity, effect, duration, remember } = data;
// Remove existing custom styles
const existingStyle = document.getElementById('opendia-custom-style');
if (existingStyle) existingStyle.remove();
let css = '';
let description = '';
try {
switch (mode) {
case 'preset':
const themeData = THEME_PRESETS[theme];
if (!themeData) throw new Error(`Unknown theme: ${theme}`);
css = themeData.css;
description = `Applied ${themeData.name} theme`;
break;
case 'custom':
css = this.buildCustomCSS({ background, text_color, font, font_size });
description = 'Applied custom styling';
break;
case 'ai_mood':
css = this.generateMoodCSS(mood, intensity);
description = `Applied AI-generated style for mood: "${mood}"`;
break;
case 'effect':
css = this.applyEffect(effect, duration);
description = `Applied ${effect} effect for ${duration}s`;
break;
case 'reset':
// CSS already removed above
description = 'Reset page to original styling';
break;
default:
throw new Error(`Unknown styling mode: ${mode}`);
}
if (css) {
const styleElement = document.createElement('style');
styleElement.id = 'opendia-custom-style';
styleElement.textContent = css;
document.head.appendChild(styleElement);
}
// Remember preference if requested
if (remember && mode !== 'reset') {
const domain = window.location.hostname;
chrome.storage.local.set({ [`style_${domain}`]: { mode, theme, css } });
}
return {
success: true,
description,
applied_css: css.length,
mode,
theme: theme || 'custom',
remember_enabled: remember,
effect_duration: duration,
mood,
intensity
};
} catch (error) {
return {
success: false,
error: error.message,
mode,
theme,
applied_css: 0
};
}
}
buildCustomCSS({ background, text_color, font, font_size }) {
let css = '';
if (background || text_color || font || font_size) {
css += '* { ';
if (background) css += `background: ${background} !important; `;
if (text_color) css += `color: ${text_color} !important; `;
if (font) css += `font-family: ${font} !important; `;
if (font_size) css += `font-size: ${font_size} !important; `;
css += '}';
}
return css;
}
generateMoodCSS(mood, intensity) {
const moodMap = {
'cozy coffee shop': {
background: '#2c1810',
text: '#f4e4bc',
accent: '#d4af37',
font: 'Georgia, serif'
},
'energetic': {
background: 'linear-gradient(45deg, #ff6b35, #f7931e)',
text: '#ffffff',
effects: 'animation: energyPulse 1s infinite;'
},
'calm ocean': {
background: 'linear-gradient(to bottom, #87ceeb, #4682b4)',
text: '#ffffff',
effects: 'animation: gentleWave 4s ease-in-out infinite;'
},
'dark professional': {
background: '#1a1a1a',
text: '#e0e0e0',
accent: '#0066cc'
},
'warm sunset': {
background: 'linear-gradient(to bottom, #ff7e5f, #feb47b)',
text: '#ffffff'
}
};
const style = moodMap[mood.toLowerCase()] || moodMap['cozy coffee shop'];
return this.buildMoodCSS(style, intensity);
}
buildMoodCSS(style, intensity) {
const opacity = intensity === 'subtle' ? '0.3' : intensity === 'medium' ? '0.6' : '0.9';
let css = `
body {
background: ${style.background} !important;
color: ${style.text} !important;
${style.font ? `font-family: ${style.font} !important;` : ''}
}
* {
color: ${style.text} !important;
}
a {
color: ${style.accent || style.text} !important;
}
`;
if (style.effects) {
css += style.effects;
}
// Add animation keyframes if needed
if (style.effects && style.effects.includes('energyPulse')) {
css += `
@keyframes energyPulse {
0%, 100% { filter: hue-rotate(0deg); }
50% { filter: hue-rotate(180deg); }
}
`;
}
if (style.effects && style.effects.includes('gentleWave')) {
css += `
@keyframes gentleWave {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
`;
}
return css;
}
applyEffect(effect, duration) {
const effects = {
matrix_rain: `
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y="10" font-size="8" fill="%2300ff00">0</text><text y="20" font-size="8" fill="%2300ff00">1</text><text y="30" font-size="8" fill="%2300ff00">0</text><text y="40" font-size="8" fill="%2300ff00">1</text></svg>');
animation: matrixFall 2s linear infinite;
pointer-events: none;
z-index: 9999;
opacity: 0.7;
}
@keyframes matrixFall {
from { transform: translateY(-100px); }
to { transform: translateY(100vh); }
}
`,
floating_particles: `
body::before {
content: '✨ 🌟 ⭐ 💫';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
animation: floatParticles 6s ease-in-out infinite;
pointer-events: none;
z-index: 9999;
font-size: 20px;
}
@keyframes floatParticles {
0%, 100% { transform: translateY(100vh) rotate(0deg); }
50% { transform: translateY(-100px) rotate(180deg); }
}
`,
cursor_trail: `
body {
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><circle cx="10" cy="10" r="8" fill="rgba(255,0,255,0.5)"/></svg>'), auto !important;
}
`,
neon_glow: `
* {
text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff, 0 0 30px #00ffff !important;
}
a, button {
box-shadow: 0 0 15px #ff00ff !important;
}
`,
typing_effect: `
* {
animation: typewriter 2s steps(40, end) infinite !important;
}
@keyframes typewriter {
from { width: 0; }
to { width: 100%; }
}
`
};
const css = effects[effect] || '';
// Auto-remove effect after duration
if (duration && duration > 0) {
setTimeout(() => {
const effectStyle = document.getElementById('opendia-custom-style');
if (effectStyle) effectStyle.remove();
}, duration * 1000);
}
return css;
}
}
// Theme Presets Database
const THEME_PRESETS = {
"dark_hacker": {
name: "🖤 Dark Hacker",
css: `
* {
background: #0a0a0a !important;
color: #00ff00 !important;
font-family: 'Courier New', monospace !important;
}
a { color: #00ffff !important; }
body::before {
content: ''; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y="50" font-size="10" fill="%23003300">01010101</text></svg>');
opacity: 0.1; pointer-events: none; z-index: -1;
}
`
},
"retro_80s": {
name: "📼 Retro 80s",
css: `
* {
background: linear-gradient(45deg, #ff0080, #8000ff) !important;
color: #ffffff !important;
font-family: 'Arial Black', sans-serif !important;
text-shadow: 2px 2px 4px #000000 !important;
}
body { animation: retroPulse 2s infinite; }
@keyframes retroPulse { 0%, 100% { filter: hue-rotate(0deg); } 50% { filter: hue-rotate(180deg); } }
`
},
"rainbow_party": {
name: "🌈 Rainbow Party",
css: `
body {
background: linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) !important;
background-size: 400% 400% !important;
animation: rainbowShift 3s ease infinite !important;
}
@keyframes rainbowShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
* { color: white !important; text-shadow: 1px 1px 2px black !important; }
`
},
"minimalist_zen": {
name: "🧘 Minimalist Zen",
css: `
* {
background: #f8f8f8 !important;
color: #333333 !important;
font-family: 'Georgia', serif !important;
line-height: 1.6 !important;
}
body { max-width: 800px; margin: 0 auto; padding: 20px; }
`
},
"high_contrast": {
name: "🔍 High Contrast",
css: `
* {
background: #000000 !important;
color: #ffffff !important;
font-family: Arial, sans-serif !important;
font-weight: bold !important;
}
a { color: #ffff00 !important; }
`
},
"cyberpunk": {
name: "🤖 Cyberpunk",
css: `
* {
background: #0d1117 !important;
color: #ff006e !important;
font-family: 'Courier New', monospace !important;
}
a { color: #00ffff !important; }
body {
background-image:
linear-gradient(90deg, transparent 79px, #abced4 79px, #abced4 81px, transparent 81px),
linear-gradient(#eee .1em, transparent .1em);
background-size: 81px 1.2em;
}
`
},
"pastel_dream": {
name: "🌸 Pastel Dream",
css: `
* {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: #2c3e50 !important;
font-family: 'Comic Sans MS', cursive !important;
}
body { filter: sepia(20%) saturate(80%); }
`
},
"newspaper": {
name: "📰 Newspaper",
css: `
* {
background: #ffffff !important;
color: #000000 !important;
font-family: 'Times New Roman', serif !important;
line-height: 1.4 !important;
}
body {
column-count: 2;
column-gap: 2em;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
`
}
};
// Initialize the automation system
const browserAutomation = new BrowserAutomation();
} // End of injection guard

File diff suppressed because one or more lines are too long

View File

@@ -236,14 +236,88 @@
button:active {
transform: translateY(0);
}
.safety-mode {
background: rgba(255, 255, 255, 0.6);
padding: 16px;
border-radius: 10px;
margin-bottom: 16px;
border: 1px solid rgba(255, 255, 255, 0.8);
backdrop-filter: blur(20px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.safety-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.safety-label {
color: #374151;
font-weight: 600;
font-size: 0.875rem;
}
.safety-toggle {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.safety-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.safety-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #e5e7eb;
transition: 0.3s;
border-radius: 24px;
border: 1px solid rgba(0, 129, 247, 0.2);
}
.safety-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
input:checked + .safety-slider {
background: linear-gradient(135deg, #0081F7, #1d4ed8);
border-color: rgba(0, 129, 247, 0.4);
}
input:checked + .safety-slider:before {
transform: translateX(26px);
}
.safety-slider:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
</style>
</head>
<body>
<div class="header">
<div class="logo">
<video autoplay loop muted playsinline>
<source src="logo.webm" type="video/webm">
<source src="logo.mp4" type="video/mp4">
<source src="../../logo.webm" type="video/webm">
<source src="../../logo.mp4" type="video/mp4">
<span>OD</span>
</video>
</div>
@@ -257,7 +331,7 @@
<span class="tooltip-content">
Start server with: npx opendia
Auto-discovery will find the correct ports.
If issues persist, try: npx opendia --kill-existing
Existing processes are automatically terminated on startup
</span>
</span>
</div>
@@ -277,6 +351,21 @@
</div>
</div>
<div class="safety-mode">
<div class="safety-row">
<span class="safety-label tooltip">
Safety Mode
<span class="tooltip-content">
When enabled, blocks write/edit tools: element_click, element_fill
</span>
</span>
<label class="safety-toggle">
<input type="checkbox" id="safetyMode">
<span class="safety-slider"></span>
</label>
</div>
</div>
<div class="button-group">
<button id="reconnectBtn">Reconnect</button>
</div>

View File

@@ -1,4 +1,13 @@
// OpenDia Popup
// Import WebExtension polyfill for cross-browser compatibility
if (typeof browser === 'undefined' && typeof chrome !== 'undefined') {
globalThis.browser = chrome;
}
// Cross-browser compatibility layer
const runtimeAPI = browser.runtime;
const tabsAPI = browser.tabs;
const storageAPI = browser.storage;
let statusIndicator = document.getElementById("statusIndicator");
let statusText = document.getElementById("statusText");
let toolCount = document.getElementById("toolCount");
@@ -14,9 +23,9 @@ function updateToolCount() {
"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) {
if (runtimeAPI?.id) {
runtimeAPI.sendMessage({ action: "getToolCount" }, (response) => {
if (!runtimeAPI.lastError && response?.toolCount) {
toolCount.innerHTML = `<span class="tooltip">${response.toolCount}
<span class="tooltip-content">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</span>
</span>`;
@@ -32,9 +41,9 @@ function updateToolCount() {
// Check connection status and get page info
function checkStatus() {
if (chrome.runtime?.id) {
chrome.runtime.sendMessage({ action: "getStatus" }, (response) => {
if (chrome.runtime.lastError) {
if (runtimeAPI?.id) {
runtimeAPI.sendMessage({ action: "getStatus" }, (response) => {
if (runtimeAPI.lastError) {
updateStatus(false);
} else {
updateStatus(response?.connected || false);
@@ -45,7 +54,7 @@ function checkStatus() {
updateToolCount();
// Get current page info
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
tabsAPI.query({active: true, currentWindow: true}, (tabs) => {
if (tabs[0]) {
const url = new URL(tabs[0].url);
currentPage.textContent = url.hostname;
@@ -62,9 +71,9 @@ setInterval(checkStatus, 2000);
// Update server URL display
function updateServerUrl() {
if (chrome.runtime?.id) {
chrome.runtime.sendMessage({ action: "getPorts" }, (response) => {
if (!chrome.runtime.lastError && response?.websocketUrl) {
if (runtimeAPI?.id) {
runtimeAPI.sendMessage({ action: "getPorts" }, (response) => {
if (!runtimeAPI.lastError && response?.websocketUrl) {
serverUrl.textContent = response.websocketUrl;
}
});
@@ -84,15 +93,15 @@ function updateStatus(connected) {
} else {
statusIndicator.className = "status-indicator disconnected";
statusText.innerHTML = `Disconnected from MCP server
<span class="tooltip-content">Start server with: npx opendia. Auto-discovery will find the correct ports. If issues persist, try: npx opendia --kill-existing</span>`;
<span class="tooltip-content">Start server with: npx opendia. Auto-discovery will find the correct ports. Existing processes are automatically terminated on startup</span>`;
}
}
// Reconnect button
document.getElementById("reconnectBtn").addEventListener("click", () => {
if (chrome.runtime?.id) {
chrome.runtime.sendMessage({ action: "reconnect" }, (response) => {
if (!chrome.runtime.lastError) {
if (runtimeAPI?.id) {
runtimeAPI.sendMessage({ action: "reconnect" }, (response) => {
if (!runtimeAPI.lastError) {
setTimeout(checkStatus, 1000);
}
});
@@ -100,8 +109,31 @@ document.getElementById("reconnectBtn").addEventListener("click", () => {
});
// Safety Mode Management
const safetyModeToggle = document.getElementById("safetyMode");
// Load safety mode state from storage
storageAPI.local.get(['safetyMode'], (result) => {
const safetyEnabled = result.safetyMode || false; // Default to false (safety off)
safetyModeToggle.checked = safetyEnabled;
});
// Handle safety mode toggle changes
safetyModeToggle.addEventListener('change', () => {
const safetyEnabled = safetyModeToggle.checked;
// Save to storage
storageAPI.local.set({ safetyMode: safetyEnabled });
// Notify background script
runtimeAPI.sendMessage({
action: "setSafetyMode",
enabled: safetyEnabled
});
});
// Listen for updates from background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
runtimeAPI.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "statusUpdate") {
updateStatus(message.connected);
}

View File

@@ -0,0 +1,182 @@
// Simple test script to verify browser extension compatibility
const fs = require('fs');
const path = require('path');
function testManifestStructure(browser) {
console.log(`\n🔍 Testing ${browser} extension structure...`);
const buildDir = `dist/${browser}`;
const manifestPath = path.join(buildDir, 'manifest.json');
try {
// Read and parse manifest
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
// Check manifest version
console.log(` Manifest version: ${manifest.manifest_version}`);
// Check background configuration
if (browser === 'chrome') {
console.log(` Background: Service Worker (${manifest.background.service_worker})`);
console.log(` Action: ${manifest.action ? 'Present' : 'Missing'}`);
console.log(` Host permissions: ${manifest.host_permissions ? manifest.host_permissions.length : 0}`);
} else {
console.log(` Background: Scripts (${manifest.background.scripts.length} files)`);
console.log(` Browser action: ${manifest.browser_action ? 'Present' : 'Missing'}`);
console.log(` Gecko ID: ${manifest.applications?.gecko?.id || 'Not set'}`);
}
// Check permissions
console.log(` Permissions: ${manifest.permissions.length} total`);
// Check content scripts
console.log(` Content scripts: ${manifest.content_scripts?.length || 0} configured`);
// Check polyfill inclusion
const polyfillPath = path.join(buildDir, 'src/polyfill/browser-polyfill.min.js');
const polyfillExists = fs.existsSync(polyfillPath);
console.log(` WebExtension polyfill: ${polyfillExists ? 'Present' : 'Missing'}`);
// Check if polyfill is included in content scripts
const hasPolyfillInContent = manifest.content_scripts?.[0]?.js?.includes('src/polyfill/browser-polyfill.min.js');
console.log(` Polyfill in content scripts: ${hasPolyfillInContent ? 'Yes' : 'No'}`);
// Check if polyfill is included in background (Firefox only)
if (browser === 'firefox') {
const hasPolyfillInBackground = manifest.background?.scripts?.includes('src/polyfill/browser-polyfill.min.js');
console.log(` Polyfill in background: ${hasPolyfillInBackground ? 'Yes' : 'No'}`);
}
console.log(`${browser} extension structure looks good!`);
return true;
} catch (error) {
console.error(`❌ Error testing ${browser} extension:`, error.message);
return false;
}
}
function testBackgroundScript(browser) {
console.log(`\n🔍 Testing ${browser} background script...`);
const scriptPath = `dist/${browser}/src/background/background.js`;
try {
const script = fs.readFileSync(scriptPath, 'utf8');
// Check for browser polyfill usage
const usesBrowserAPI = script.includes('browser.') || script.includes('globalThis.browser');
console.log(` Uses browser API: ${usesBrowserAPI ? 'Yes' : 'No'}`);
// Check for connection manager
const hasConnectionManager = script.includes('ConnectionManager');
console.log(` Has connection manager: ${hasConnectionManager ? 'Yes' : 'No'}`);
// Check for browser detection
const hasBrowserDetection = script.includes('browserInfo') || script.includes('isFirefox') || script.includes('isServiceWorker');
console.log(` Has browser detection: ${hasBrowserDetection ? 'Yes' : 'No'}`);
// Check for WebSocket management
const hasWebSocketManagement = script.includes('WebSocket') && script.includes('connect');
console.log(` Has WebSocket management: ${hasWebSocketManagement ? 'Yes' : 'No'}`);
console.log(`${browser} background script looks good!`);
return true;
} catch (error) {
console.error(`❌ Error testing ${browser} background script:`, error.message);
return false;
}
}
function testContentScript(browser) {
console.log(`\n🔍 Testing ${browser} content script...`);
const scriptPath = `dist/${browser}/src/content/content.js`;
try {
const script = fs.readFileSync(scriptPath, 'utf8');
// Check for browser polyfill usage
const usesBrowserAPI = script.includes('browser.') || script.includes('globalThis.browser');
console.log(` Uses browser API: ${usesBrowserAPI ? 'Yes' : 'No'}`);
// Check for message handling
const hasMessageHandling = script.includes('onMessage') && script.includes('sendResponse');
console.log(` Has message handling: ${hasMessageHandling ? 'Yes' : 'No'}`);
console.log(`${browser} content script looks good!`);
return true;
} catch (error) {
console.error(`❌ Error testing ${browser} content script:`, error.message);
return false;
}
}
function testPopupScript(browser) {
console.log(`\n🔍 Testing ${browser} popup script...`);
const scriptPath = `dist/${browser}/src/popup/popup.js`;
try {
const script = fs.readFileSync(scriptPath, 'utf8');
// Check for browser polyfill usage
const usesBrowserAPI = script.includes('browser.') || script.includes('globalThis.browser');
console.log(` Uses browser API: ${usesBrowserAPI ? 'Yes' : 'No'}`);
// Check for API abstraction
const hasAPIAbstraction = script.includes('runtimeAPI') || script.includes('tabsAPI') || script.includes('storageAPI');
console.log(` Has API abstraction: ${hasAPIAbstraction ? 'Yes' : 'No'}`);
console.log(`${browser} popup script looks good!`);
return true;
} catch (error) {
console.error(`❌ Error testing ${browser} popup script:`, error.message);
return false;
}
}
function runAllTests() {
console.log('🚀 Testing cross-browser extension compatibility...\n');
const browsers = ['chrome', 'firefox'];
let allPassed = true;
for (const browser of browsers) {
console.log(`\n🌐 Testing ${browser.toUpperCase()} extension:`);
console.log('='.repeat(40));
const tests = [
testManifestStructure,
testBackgroundScript,
testContentScript,
testPopupScript
];
for (const test of tests) {
if (!test(browser)) {
allPassed = false;
}
}
}
console.log('\n' + '='.repeat(50));
if (allPassed) {
console.log('🎉 All tests passed! Cross-browser extension is ready.');
console.log('\n📦 Distribution packages:');
console.log(' Chrome: dist/opendia-chrome.zip');
console.log(' Firefox: dist/opendia-firefox.zip');
console.log('\n🧪 Manual testing:');
console.log(' 1. Chrome: Load dist/chrome in chrome://extensions');
console.log(' 2. Firefox: Load dist/firefox in about:debugging');
console.log(' 3. Both should connect to MCP server on localhost:5555/5556');
} else {
console.log('❌ Some tests failed. Please check the output above.');
}
}
// Run tests
runAllTests();

View File

@@ -1,12 +1,12 @@
{
"name": "opendia",
"version": "1.0.3",
"version": "1.0.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "opendia",
"version": "1.0.3",
"version": "1.0.6",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
@@ -86,7 +86,7 @@
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "opendia",
"version": "1.0.4",
"version": "1.1.0",
"description": "🎯 OpenDia - The open alternative to Dia. Connect your browser to AI models with anti-detection bypass for Twitter/X, LinkedIn, Facebook",
"main": "server.js",
"bin": {

File diff suppressed because it is too large Load Diff

BIN
opendia.dxt Normal file

Binary file not shown.