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>
This commit is contained in:
Aaron Elijah Mars
2025-07-16 22:57:39 +02:00
parent 99d31b15c8
commit 0b09201160
24 changed files with 5617 additions and 271 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,375 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>OpenDia</title>
<style>
:root {
--primary-color: #2563eb;
--success-color: #22c55e;
--error-color: #ef4444;
--bg-color: #ffffff;
--text-color: #1f2937;
--border-color: #e5e7eb;
--hover-color: #1d4ed8;
}
body {
width: 380px;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--text-color);
background: linear-gradient(135deg, #0081F7 0%, #FF75CA 50%, #FFAE87 100%);
margin: 0;
min-height: 300px;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(20px);
z-index: 0;
}
body > * {
position: relative;
z-index: 1;
}
.header {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.logo {
width: 32px;
height: 32px;
margin-right: 12px;
background: linear-gradient(135deg, #0081F7, #FF75CA);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
box-shadow: 0 2px 8px rgba(0, 129, 247, 0.3);
overflow: hidden;
position: relative;
}
.logo video {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
left: 0;
border-radius: 50%;
}
.logo span {
position: relative;
z-index: 1;
}
h2 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
.status {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 12px;
background: rgba(255, 255, 255, 0.6);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.8);
backdrop-filter: blur(20px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 12px;
transition: background-color 0.3s ease;
}
.connected {
background-color: var(--success-color);
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.1);
}
.disconnected {
background-color: var(--error-color);
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1);
}
.info {
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);
}
.info-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-label {
color: #6b7280;
font-size: 0.875rem;
}
.info-value {
font-weight: 500;
}
.tooltip {
position: relative;
cursor: help;
text-decoration: underline;
text-decoration-style: dotted;
text-underline-offset: 2px;
text-decoration-color: #9ca3af;
}
.tooltip-content {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
transform: translateY(-5px);
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 8px 10px;
border-radius: 6px;
font-size: 0.65rem;
font-weight: normal;
white-space: pre-wrap;
width: 320px;
margin: 0 auto;
text-align: center;
line-height: 1.3;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s, visibility 0.2s;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
word-wrap: break-word;
}
.info-row .tooltip .tooltip-content {
left: -350px;
margin: 0;
}
.tooltip:hover .tooltip-content {
opacity: 1;
visibility: visible;
}
.button-group {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
button {
background: rgba(255, 255, 255, 0.3);
color: #0081F7;
border: 1px solid rgba(0, 129, 247, 0.2);
padding: 10px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
font-size: 0.875rem;
transition: all 0.3s ease;
flex: 1;
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
}
button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 117, 202, 0.3), transparent);
transition: left 0.6s ease;
}
button:hover {
transform: translateY(-2px);
background: rgba(255, 255, 255, 0.5);
border-color: rgba(255, 117, 202, 0.4);
box-shadow: 0 6px 20px rgba(255, 117, 202, 0.2);
}
button:hover::before {
left: 100%;
}
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">
<span>OD</span>
</video>
</div>
<h2>OpenDia</h2>
</div>
<div class="status">
<div class="status-indicator" id="statusIndicator"></div>
<span id="statusText" class="tooltip">
Checking connection...
<span class="tooltip-content">
Start server with: npx opendia
Auto-discovery will find the correct ports.
Existing processes are automatically terminated on startup
</span>
</span>
</div>
<div class="info">
<div class="info-row">
<span class="info-label">Server</span>
<span class="info-value" id="serverUrl">Auto-Discovery</span>
</div>
<div class="info-row">
<span class="info-label">Available Tools</span>
<span class="info-value" id="toolCount">Loading...</span>
</div>
<div class="info-row">
<span class="info-label">Current Page</span>
<span class="info-value" id="currentPage">Loading...</span>
</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>
<script src="popup.js"></script>
</body>
</html>

View File

@@ -0,0 +1,179 @@
// 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");
let currentPage = document.getElementById("currentPage");
let serverUrl = document.getElementById("serverUrl");
// 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 (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>`;
} else {
// Fallback to calculating from background script
toolCount.innerHTML = `<span class="tooltip">17
<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>`;
}
});
}
}
// Check connection status and get page info
function checkStatus() {
if (runtimeAPI?.id) {
runtimeAPI.sendMessage({ action: "getStatus" }, (response) => {
if (runtimeAPI.lastError) {
updateStatus(false);
} else {
updateStatus(response?.connected || false);
}
});
// Update tool count
updateToolCount();
// Get current page info
tabsAPI.query({active: true, currentWindow: true}, (tabs) => {
if (tabs[0]) {
const url = new URL(tabs[0].url);
currentPage.textContent = url.hostname;
}
});
} else {
updateStatus(false);
}
}
// Check status on load and periodically
checkStatus();
setInterval(checkStatus, 2000);
// Update server URL display
function updateServerUrl() {
if (runtimeAPI?.id) {
runtimeAPI.sendMessage({ action: "getPorts" }, (response) => {
if (!runtimeAPI.lastError && response?.websocketUrl) {
serverUrl.textContent = response.websocketUrl;
}
});
}
}
// Update server URL periodically
updateServerUrl();
setInterval(updateServerUrl, 5000);
// Update UI based on connection status
function updateStatus(connected) {
if (connected) {
statusIndicator.className = "status-indicator connected";
statusText.innerHTML = `Connected to MCP server
<span class="tooltip-content">Connected successfully! Server auto-discovery is working. Default ports: WebSocket=5555, HTTP=5556</span>`;
} 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. Existing processes are automatically terminated on startup</span>`;
}
}
// Reconnect button
document.getElementById("reconnectBtn").addEventListener("click", () => {
if (runtimeAPI?.id) {
runtimeAPI.sendMessage({ action: "reconnect" }, (response) => {
if (!runtimeAPI.lastError) {
setTimeout(checkStatus, 1000);
}
});
}
});
// 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
runtimeAPI.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "statusUpdate") {
updateStatus(message.connected);
}
});
// 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;
}