@different-ai/opencode-browser
Advanced tools
+1
-1
| { | ||
| "name": "@different-ai/opencode-browser", | ||
| "version": "2.0.1", | ||
| "version": "2.0.2", | ||
| "description": "Browser automation plugin for OpenCode. Control your real Chrome browser with existing logins and cookies.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+78
-24
@@ -37,2 +37,3 @@ /** | ||
| let hasLock = false; | ||
| let serverFailed = false; | ||
@@ -123,3 +124,3 @@ // ============================================================================ | ||
| process.kill(targetPid, "SIGTERM"); | ||
| // Wait a bit for process to die | ||
| // Wait for process to die | ||
| let attempts = 0; | ||
@@ -146,3 +147,21 @@ while (isProcessAlive(targetPid) && attempts < 10) { | ||
| function checkPortAvailable(): boolean { | ||
| try { | ||
| const testSocket = Bun.connect({ port: WS_PORT, timeout: 1000 }); | ||
| testSocket.end(); | ||
| return true; | ||
| } catch (e) { | ||
| if ((e as any).code === "ECONNREFUSED") { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
| function startServer(): boolean { | ||
| if (server) { | ||
| console.error(`[browser-plugin] Server already running`); | ||
| return true; | ||
| } | ||
| try { | ||
@@ -177,2 +196,3 @@ server = Bun.serve({ | ||
| console.error(`[browser-plugin] WebSocket server listening on port ${WS_PORT}`); | ||
| serverFailed = false; | ||
| return true; | ||
@@ -185,3 +205,3 @@ } catch (e) { | ||
| function handleMessage(message: { type: string; id?: number; result?: any; error?: any }) { | ||
| function handleMessage(message: { type: string; id?: number; result?: any; error?: any }): void { | ||
| if (message.type === "tool_response" && message.id !== undefined) { | ||
@@ -211,3 +231,3 @@ const pending = pendingRequests.get(message.id); | ||
| async function executeCommand(tool: string, args: Record<string, any>): Promise<any> { | ||
| // Check lock first | ||
| // Check lock and start server if needed | ||
| const lockResult = tryAcquireLock(); | ||
@@ -220,3 +240,2 @@ if (!lockResult.success) { | ||
| // Start server if not running | ||
| if (!server) { | ||
@@ -283,8 +302,29 @@ if (!startServer()) { | ||
| // Try to acquire lock and start server on load | ||
| const lockResult = tryAcquireLock(); | ||
| if (lockResult.success) { | ||
| startServer(); | ||
| // Check port availability on load, don't try to acquire lock yet | ||
| checkPortAvailable(); | ||
| // Check lock status and set appropriate state | ||
| const lock = readLock(); | ||
| if (!lock) { | ||
| // No lock - just check if we can start server | ||
| console.error(`[browser-plugin] No lock file, checking port...`); | ||
| if (!startServer()) { | ||
| serverFailed = true; | ||
| } | ||
| } else if (lock.sessionId === sessionId) { | ||
| // We own the lock - start server | ||
| console.error(`[browser-plugin] Already have lock, starting server...`); | ||
| if (!startServer()) { | ||
| serverFailed = true; | ||
| } | ||
| } else if (!isProcessAlive(lock.pid)) { | ||
| // Stale lock - take it and start server | ||
| console.error(`[browser-plugin] Stale lock from dead PID ${lock.pid}, taking over...`); | ||
| writeLock(); | ||
| if (!startServer()) { | ||
| serverFailed = true; | ||
| } | ||
| } else { | ||
| console.error(`[browser-plugin] Lock held by PID ${lockResult.lock?.pid}, tools will fail until lock is released`); | ||
| // Another session has the lock | ||
| console.error(`[browser-plugin] Lock held by PID ${lock.pid}, tools will fail until lock is released`); | ||
| } | ||
@@ -327,3 +367,8 @@ | ||
| writeLock(); | ||
| if (!server) startServer(); | ||
| // Start server if needed | ||
| if (!server) { | ||
| if (!startServer()) { | ||
| throw new Error("Failed to start WebSocket server after acquiring lock."); | ||
| } | ||
| } | ||
| return "No active session. Browser now connected to this session."; | ||
@@ -339,10 +384,19 @@ } | ||
| writeLock(); | ||
| if (!server) startServer(); | ||
| // Start server if needed | ||
| if (!server) { | ||
| if (!startServer()) { | ||
| throw new Error("Failed to start WebSocket server after cleaning stale lock."); | ||
| } | ||
| } | ||
| return `Cleaned stale lock (PID ${lock.pid} was dead). Browser now connected to this session.`; | ||
| } | ||
| // Kill the other session | ||
| // Kill other session and wait for port to be free | ||
| const result = await killSession(lock.pid); | ||
| if (result.success) { | ||
| if (!server) startServer(); | ||
| if (!server) { | ||
| if (!startServer()) { | ||
| throw new Error("Failed to start WebSocket server after killing other session."); | ||
| } | ||
| } | ||
| return `Killed session ${lock.sessionId} (PID ${lock.pid}). Browser now connected to this session.`; | ||
@@ -356,3 +410,3 @@ } else { | ||
| browser_navigate: tool({ | ||
| description: "Navigate to a URL in the browser", | ||
| description: "Navigate to a URL in browser", | ||
| args: { | ||
@@ -368,5 +422,5 @@ url: tool.schema.string({ description: "The URL to navigate to" }), | ||
| browser_click: tool({ | ||
| description: "Click an element on the page using a CSS selector", | ||
| description: "Click an element on page using a CSS selector", | ||
| args: { | ||
| selector: tool.schema.string({ description: "CSS selector for the element to click" }), | ||
| selector: tool.schema.string({ description: "CSS selector for element to click" }), | ||
| tabId: tool.schema.optional(tool.schema.number({ description: "Optional tab ID" })), | ||
@@ -382,3 +436,3 @@ }, | ||
| args: { | ||
| selector: tool.schema.string({ description: "CSS selector for the input element" }), | ||
| selector: tool.schema.string({ description: "CSS selector for input element" }), | ||
| text: tool.schema.string({ description: "Text to type" }), | ||
@@ -394,12 +448,13 @@ clear: tool.schema.optional(tool.schema.boolean({ description: "Clear field before typing" })), | ||
| browser_screenshot: tool({ | ||
| description: "Take a screenshot of the current page. Saves to ~/.opencode-browser/screenshots/ and returns the file path.", | ||
| description: "Take a screenshot of the current page. Saves to ~/.opencode-browser/screenshots/", | ||
| args: { | ||
| tabId: tool.schema.optional(tool.schema.number({ description: "Optional tab ID" })), | ||
| name: tool.schema.optional(tool.schema.string({ description: "Optional name for the screenshot file (without extension)" })), | ||
| name: tool.schema.optional( | ||
| tool.schema.string({ description: "Optional name for screenshot file (without extension)" }) | ||
| ), | ||
| }, | ||
| async execute(args) { | ||
| const result = await executeCommand("screenshot", args); | ||
| if (result && result.startsWith("data:image")) { | ||
| // Extract base64 data and save to file | ||
| const base64Data = result.replace(/^data:image\/\w+;base64,/, ""); | ||
@@ -409,8 +464,7 @@ const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); | ||
| const filepath = join(SCREENSHOTS_DIR, filename); | ||
| writeFileSync(filepath, Buffer.from(base64Data, "base64")); | ||
| return `Screenshot saved: ${filepath}`; | ||
| } | ||
| return result; | ||
@@ -417,0 +471,0 @@ }, |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
41679
3.89%1013
5.41%