@different-ai/opencode-browser 1.0.5 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -52
- package/bin/cli.js +151 -251
- package/extension/background.js +3 -3
- package/extension/manifest.json +2 -3
- package/package.json +20 -9
- package/src/plugin.ts +461 -0
- package/src/daemon.js +0 -207
- package/src/host.js +0 -282
- package/src/server.js +0 -379
package/bin/cli.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* OpenCode Browser - CLI Installer
|
|
4
|
-
*
|
|
5
|
-
* Installs the Chrome extension
|
|
4
|
+
*
|
|
5
|
+
* Installs the Chrome extension for browser automation.
|
|
6
|
+
* v2.0: Plugin-based architecture (no daemon, no MCP server)
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, copyFileSync, readdirSync } from "fs";
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, copyFileSync, readdirSync, unlinkSync } from "fs";
|
|
10
10
|
import { homedir, platform } from "os";
|
|
11
11
|
import { join, dirname } from "path";
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
13
|
import { execSync } from "child_process";
|
|
14
|
+
import { createInterface } from "readline";
|
|
14
15
|
|
|
15
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
17
|
const __dirname = dirname(__filename);
|
|
@@ -35,20 +36,20 @@ function log(msg) {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
function success(msg) {
|
|
38
|
-
console.log(color("green", "
|
|
39
|
+
console.log(color("green", " " + msg));
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
function warn(msg) {
|
|
42
|
-
console.log(color("yellow", "
|
|
43
|
+
console.log(color("yellow", " " + msg));
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function error(msg) {
|
|
46
|
-
console.log(color("red", "
|
|
47
|
+
console.log(color("red", " " + msg));
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
function header(msg) {
|
|
50
51
|
console.log("\n" + color("cyan", color("bright", msg)));
|
|
51
|
-
console.log(color("cyan", "
|
|
52
|
+
console.log(color("cyan", "-".repeat(msg.length)));
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
const rl = createInterface({
|
|
@@ -71,12 +72,8 @@ async function confirm(question) {
|
|
|
71
72
|
|
|
72
73
|
async function main() {
|
|
73
74
|
console.log(`
|
|
74
|
-
${color("cyan", color("bright", "
|
|
75
|
-
${color("cyan",
|
|
76
|
-
${color("cyan", color("bright", "║"))} ${color("cyan", color("bright", "║"))}
|
|
77
|
-
${color("cyan", color("bright", "║"))} Inspired by Claude in Chrome - browser automation that ${color("cyan", color("bright", "║"))}
|
|
78
|
-
${color("cyan", color("bright", "║"))} works with your existing logins and bookmarks. ${color("cyan", color("bright", "║"))}
|
|
79
|
-
${color("cyan", color("bright", "╚═══════════════════════════════════════════════════════════╝"))}
|
|
75
|
+
${color("cyan", color("bright", "OpenCode Browser v2.0"))}
|
|
76
|
+
${color("cyan", "Browser automation for OpenCode")}
|
|
80
77
|
`);
|
|
81
78
|
|
|
82
79
|
const command = process.argv[2];
|
|
@@ -85,28 +82,18 @@ ${color("cyan", color("bright", "╚══════════════
|
|
|
85
82
|
await install();
|
|
86
83
|
} else if (command === "uninstall") {
|
|
87
84
|
await uninstall();
|
|
88
|
-
} else if (command === "
|
|
89
|
-
await
|
|
90
|
-
} else if (command === "daemon-install") {
|
|
91
|
-
await installDaemon();
|
|
92
|
-
} else if (command === "start") {
|
|
93
|
-
rl.close();
|
|
94
|
-
await import("../src/server.js");
|
|
95
|
-
return;
|
|
85
|
+
} else if (command === "status") {
|
|
86
|
+
await status();
|
|
96
87
|
} else {
|
|
97
88
|
log(`
|
|
98
89
|
${color("bright", "Usage:")}
|
|
99
|
-
npx @different-ai/opencode-browser install
|
|
100
|
-
npx @different-ai/opencode-browser
|
|
101
|
-
npx @different-ai/opencode-browser
|
|
102
|
-
npx @different-ai/opencode-browser start Run MCP server
|
|
103
|
-
npx @different-ai/opencode-browser uninstall Remove installation
|
|
90
|
+
npx @different-ai/opencode-browser install Install extension
|
|
91
|
+
npx @different-ai/opencode-browser uninstall Remove installation
|
|
92
|
+
npx @different-ai/opencode-browser status Check lock status
|
|
104
93
|
|
|
105
|
-
${color("bright", "
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
${color("bright", "For scheduled jobs:")}
|
|
109
|
-
Run 'daemon-install' to enable browser tools in background jobs.
|
|
94
|
+
${color("bright", "v2.0 Changes:")}
|
|
95
|
+
- Plugin-based architecture (no daemon needed)
|
|
96
|
+
- Add plugin to opencode.json, load extension in Chrome, done
|
|
110
97
|
`);
|
|
111
98
|
}
|
|
112
99
|
|
|
@@ -124,7 +111,7 @@ async function install() {
|
|
|
124
111
|
}
|
|
125
112
|
success(`Platform: ${os === "darwin" ? "macOS" : "Linux"}`);
|
|
126
113
|
|
|
127
|
-
header("Step 2:
|
|
114
|
+
header("Step 2: Copy Extension Files");
|
|
128
115
|
|
|
129
116
|
const extensionDir = join(homedir(), ".opencode-browser", "extension");
|
|
130
117
|
const srcExtensionDir = join(PACKAGE_ROOT, "extension");
|
|
@@ -135,7 +122,7 @@ async function install() {
|
|
|
135
122
|
for (const file of files) {
|
|
136
123
|
const srcPath = join(srcExtensionDir, file);
|
|
137
124
|
const destPath = join(extensionDir, file);
|
|
138
|
-
|
|
125
|
+
|
|
139
126
|
try {
|
|
140
127
|
const stat = readdirSync(srcPath);
|
|
141
128
|
mkdirSync(destPath, { recursive: true });
|
|
@@ -147,7 +134,7 @@ async function install() {
|
|
|
147
134
|
|
|
148
135
|
success(`Extension files copied to: ${extensionDir}`);
|
|
149
136
|
|
|
150
|
-
header("Step 3: Load Extension in
|
|
137
|
+
header("Step 3: Load Extension in Chrome");
|
|
151
138
|
|
|
152
139
|
log(`
|
|
153
140
|
Works with: ${color("cyan", "Chrome")}, ${color("cyan", "Brave")}, ${color("cyan", "Arc")}, ${color("cyan", "Edge")}, and other Chromium browsers.
|
|
@@ -164,281 +151,194 @@ To load the extension:
|
|
|
164
151
|
4. Select this folder:
|
|
165
152
|
${color("cyan", extensionDir)}
|
|
166
153
|
${os === "darwin" ? color("yellow", "Tip: Press Cmd+Shift+G and paste the path above") : ""}
|
|
167
|
-
|
|
168
|
-
5. Copy the ${color("bright", "Extension ID")} shown under the extension name
|
|
169
|
-
(looks like: abcdefghijklmnopqrstuvwxyz123456)
|
|
170
154
|
`);
|
|
171
155
|
|
|
172
|
-
|
|
173
|
-
const extensionId = await ask(color("bright", "Enter your Extension ID: "));
|
|
174
|
-
|
|
175
|
-
if (!extensionId) {
|
|
176
|
-
error("Extension ID is required");
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!/^[a-z]{32}$/.test(extensionId)) {
|
|
181
|
-
warn("Extension ID format looks unusual (expected 32 lowercase letters)");
|
|
182
|
-
const proceed = await confirm("Continue anyway?");
|
|
183
|
-
if (!proceed) process.exit(1);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
header("Step 4: Register Native Messaging Host");
|
|
187
|
-
|
|
188
|
-
const nativeHostDir = os === "darwin"
|
|
189
|
-
? join(homedir(), "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts")
|
|
190
|
-
: join(homedir(), ".config", "google-chrome", "NativeMessagingHosts");
|
|
191
|
-
|
|
192
|
-
mkdirSync(nativeHostDir, { recursive: true });
|
|
193
|
-
|
|
194
|
-
const nodePath = process.execPath;
|
|
195
|
-
const hostScriptPath = join(PACKAGE_ROOT, "src", "host.js");
|
|
196
|
-
|
|
197
|
-
const wrapperDir = join(homedir(), ".opencode-browser");
|
|
198
|
-
const wrapperPath = join(wrapperDir, "host-wrapper.sh");
|
|
199
|
-
|
|
200
|
-
writeFileSync(wrapperPath, `#!/bin/bash
|
|
201
|
-
exec "${nodePath}" "${hostScriptPath}" "$@"
|
|
202
|
-
`, { mode: 0o755 });
|
|
203
|
-
|
|
204
|
-
const manifest = {
|
|
205
|
-
name: "com.opencode.browser_automation",
|
|
206
|
-
description: "OpenCode Browser Automation Native Messaging Host",
|
|
207
|
-
path: wrapperPath,
|
|
208
|
-
type: "stdio",
|
|
209
|
-
allowed_origins: [`chrome-extension://${extensionId}/`],
|
|
210
|
-
};
|
|
156
|
+
await ask(color("bright", "Press Enter when you've loaded the extension..."));
|
|
211
157
|
|
|
212
|
-
|
|
213
|
-
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
214
|
-
|
|
215
|
-
success(`Native host registered at: ${manifestPath}`);
|
|
216
|
-
|
|
217
|
-
const logsDir = join(homedir(), ".opencode-browser", "logs");
|
|
218
|
-
mkdirSync(logsDir, { recursive: true });
|
|
158
|
+
header("Step 4: Configure OpenCode");
|
|
219
159
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
226
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
227
|
-
<plist version="1.0">
|
|
228
|
-
<dict>
|
|
229
|
-
<key>Label</key>
|
|
230
|
-
<string>com.opencode.browser-daemon</string>
|
|
231
|
-
<key>ProgramArguments</key>
|
|
232
|
-
<array>
|
|
233
|
-
<string>${nodePath}</string>
|
|
234
|
-
<string>${daemonPath}</string>
|
|
235
|
-
</array>
|
|
236
|
-
<key>RunAtLoad</key>
|
|
237
|
-
<true/>
|
|
238
|
-
<key>KeepAlive</key>
|
|
239
|
-
<true/>
|
|
240
|
-
<key>StandardOutPath</key>
|
|
241
|
-
<string>${logsDir}/daemon.log</string>
|
|
242
|
-
<key>StandardErrorPath</key>
|
|
243
|
-
<string>${logsDir}/daemon.log</string>
|
|
244
|
-
</dict>
|
|
245
|
-
</plist>`;
|
|
246
|
-
|
|
247
|
-
const plistPath = join(homedir(), "Library", "LaunchAgents", "com.opencode.browser-daemon.plist");
|
|
248
|
-
writeFileSync(plistPath, plist);
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: "ignore" });
|
|
252
|
-
execSync(`launchctl load "${plistPath}"`, { stdio: "ignore" });
|
|
253
|
-
success("Daemon installed and started");
|
|
254
|
-
} catch (e) {
|
|
255
|
-
warn("Could not start daemon automatically. Run: launchctl load " + plistPath);
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
warn("On Linux, create a systemd service for the daemon manually.");
|
|
259
|
-
log(`Daemon script: ${join(PACKAGE_ROOT, "src", "daemon.js")}`);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
header("Step 6: Configure OpenCode");
|
|
263
|
-
|
|
264
|
-
const serverPath = join(PACKAGE_ROOT, "src", "server.js");
|
|
265
|
-
const mcpConfig = {
|
|
266
|
-
browser: {
|
|
267
|
-
type: "local",
|
|
268
|
-
command: ["node", serverPath],
|
|
269
|
-
enabled: true,
|
|
270
|
-
},
|
|
271
|
-
};
|
|
160
|
+
const pluginConfig = `{
|
|
161
|
+
"$schema": "https://opencode.ai/config.json",
|
|
162
|
+
"plugin": ["@different-ai/opencode-browser"]
|
|
163
|
+
}`;
|
|
272
164
|
|
|
273
165
|
log(`
|
|
274
|
-
Add
|
|
166
|
+
Add the plugin to your ${color("cyan", "opencode.json")}:
|
|
275
167
|
|
|
276
|
-
${color("bright",
|
|
168
|
+
${color("bright", pluginConfig)}
|
|
169
|
+
|
|
170
|
+
Or if you already have an opencode.json, just add to the "plugin" array:
|
|
171
|
+
${color("bright", '"plugin": ["@different-ai/opencode-browser"]')}
|
|
277
172
|
`);
|
|
278
173
|
|
|
279
174
|
const opencodeJsonPath = join(process.cwd(), "opencode.json");
|
|
280
|
-
let shouldUpdateConfig = false;
|
|
281
175
|
|
|
282
176
|
if (existsSync(opencodeJsonPath)) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (
|
|
177
|
+
const shouldUpdate = await confirm(`Found opencode.json. Add plugin automatically?`);
|
|
178
|
+
|
|
179
|
+
if (shouldUpdate) {
|
|
286
180
|
try {
|
|
287
181
|
const config = JSON.parse(readFileSync(opencodeJsonPath, "utf-8"));
|
|
288
|
-
config.
|
|
289
|
-
config.
|
|
182
|
+
config.plugin = config.plugin || [];
|
|
183
|
+
if (!config.plugin.includes("@different-ai/opencode-browser")) {
|
|
184
|
+
config.plugin.push("@different-ai/opencode-browser");
|
|
185
|
+
}
|
|
186
|
+
// Remove old MCP config if present
|
|
187
|
+
if (config.mcp?.browser) {
|
|
188
|
+
delete config.mcp.browser;
|
|
189
|
+
if (Object.keys(config.mcp).length === 0) {
|
|
190
|
+
delete config.mcp;
|
|
191
|
+
}
|
|
192
|
+
warn("Removed old MCP browser config (replaced by plugin)");
|
|
193
|
+
}
|
|
290
194
|
writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
291
|
-
success("Updated opencode.json with
|
|
195
|
+
success("Updated opencode.json with plugin");
|
|
292
196
|
} catch (e) {
|
|
293
197
|
error(`Failed to update opencode.json: ${e.message}`);
|
|
294
|
-
log("Please add the
|
|
198
|
+
log("Please add the plugin manually.");
|
|
295
199
|
}
|
|
296
200
|
}
|
|
297
201
|
} else {
|
|
298
|
-
const shouldCreate = await confirm(`No opencode.json found. Create one
|
|
299
|
-
|
|
202
|
+
const shouldCreate = await confirm(`No opencode.json found. Create one?`);
|
|
203
|
+
|
|
300
204
|
if (shouldCreate) {
|
|
301
205
|
try {
|
|
302
|
-
const config = {
|
|
206
|
+
const config = {
|
|
207
|
+
$schema: "https://opencode.ai/config.json",
|
|
208
|
+
plugin: ["@different-ai/opencode-browser"],
|
|
209
|
+
};
|
|
303
210
|
writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
304
|
-
success("Created opencode.json with
|
|
305
|
-
shouldUpdateConfig = true;
|
|
211
|
+
success("Created opencode.json with plugin");
|
|
306
212
|
} catch (e) {
|
|
307
213
|
error(`Failed to create opencode.json: ${e.message}`);
|
|
308
214
|
}
|
|
309
|
-
} else {
|
|
310
|
-
log(`Add the config above to your project's opencode.json manually.`);
|
|
311
215
|
}
|
|
312
216
|
}
|
|
313
217
|
|
|
218
|
+
// Clean up old daemon if present
|
|
219
|
+
header("Step 5: Cleanup (v1.x migration)");
|
|
220
|
+
|
|
221
|
+
const oldDaemonPlist = join(homedir(), "Library", "LaunchAgents", "com.opencode.browser-daemon.plist");
|
|
222
|
+
if (existsSync(oldDaemonPlist)) {
|
|
223
|
+
try {
|
|
224
|
+
execSync(`launchctl unload "${oldDaemonPlist}" 2>/dev/null || true`, { stdio: "ignore" });
|
|
225
|
+
unlinkSync(oldDaemonPlist);
|
|
226
|
+
success("Removed old daemon (no longer needed in v2.0)");
|
|
227
|
+
} catch {
|
|
228
|
+
warn("Could not remove old daemon plist. Remove manually if needed.");
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
success("No old daemon to clean up");
|
|
232
|
+
}
|
|
233
|
+
|
|
314
234
|
header("Installation Complete!");
|
|
315
235
|
|
|
316
236
|
log(`
|
|
317
|
-
${color("green", "
|
|
318
|
-
${color("green", "
|
|
319
|
-
${shouldUpdateConfig ? color("green", "✓") + " opencode.json updated" : color("yellow", "○") + " Remember to update opencode.json"}
|
|
237
|
+
${color("green", "")} Extension: ${extensionDir}
|
|
238
|
+
${color("green", "")} Plugin: @different-ai/opencode-browser
|
|
320
239
|
|
|
321
|
-
${color("bright", "
|
|
322
|
-
1.
|
|
323
|
-
2.
|
|
324
|
-
3.
|
|
240
|
+
${color("bright", "How it works:")}
|
|
241
|
+
1. OpenCode loads the plugin on startup
|
|
242
|
+
2. Plugin starts WebSocket server on port 19222
|
|
243
|
+
3. Chrome extension connects automatically
|
|
244
|
+
4. Browser tools are available!
|
|
325
245
|
|
|
326
246
|
${color("bright", "Available tools:")}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
${color("bright", "
|
|
340
|
-
|
|
247
|
+
browser_status - Check if browser is available
|
|
248
|
+
browser_kill_session - Take over from another session
|
|
249
|
+
browser_navigate - Go to a URL
|
|
250
|
+
browser_click - Click an element
|
|
251
|
+
browser_type - Type into an input
|
|
252
|
+
browser_screenshot - Capture the page
|
|
253
|
+
browser_snapshot - Get accessibility tree
|
|
254
|
+
browser_get_tabs - List open tabs
|
|
255
|
+
browser_scroll - Scroll the page
|
|
256
|
+
browser_wait - Wait for duration
|
|
257
|
+
browser_execute - Run JavaScript
|
|
258
|
+
|
|
259
|
+
${color("bright", "Multi-session:")}
|
|
260
|
+
Only one OpenCode session can use browser at a time.
|
|
261
|
+
Use browser_status to check, browser_kill_session to take over.
|
|
262
|
+
|
|
263
|
+
${color("bright", "Test it:")}
|
|
264
|
+
Restart OpenCode and try: ${color("cyan", '"Check browser status"')}
|
|
341
265
|
`);
|
|
342
266
|
}
|
|
343
267
|
|
|
344
|
-
async function
|
|
345
|
-
|
|
346
|
-
const daemonPath = join(PACKAGE_ROOT, "src", "daemon.js");
|
|
347
|
-
log("Starting daemon...");
|
|
348
|
-
const child = spawn(process.execPath, [daemonPath], { stdio: "inherit" });
|
|
349
|
-
child.on("exit", (code) => process.exit(code || 0));
|
|
350
|
-
}
|
|
268
|
+
async function status() {
|
|
269
|
+
header("Browser Lock Status");
|
|
351
270
|
|
|
352
|
-
|
|
353
|
-
header("Installing Background Daemon");
|
|
354
|
-
|
|
355
|
-
const os = platform();
|
|
356
|
-
if (os !== "darwin") {
|
|
357
|
-
error("Daemon auto-install currently supports macOS only");
|
|
358
|
-
log("On Linux, create a systemd service manually.");
|
|
359
|
-
process.exit(1);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const nodePath = process.execPath;
|
|
363
|
-
const daemonPath = join(PACKAGE_ROOT, "src", "daemon.js");
|
|
364
|
-
const logsDir = join(homedir(), ".opencode-browser", "logs");
|
|
365
|
-
|
|
366
|
-
mkdirSync(logsDir, { recursive: true });
|
|
367
|
-
|
|
368
|
-
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
369
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
370
|
-
<plist version="1.0">
|
|
371
|
-
<dict>
|
|
372
|
-
<key>Label</key>
|
|
373
|
-
<string>com.opencode.browser-daemon</string>
|
|
374
|
-
<key>ProgramArguments</key>
|
|
375
|
-
<array>
|
|
376
|
-
<string>${nodePath}</string>
|
|
377
|
-
<string>${daemonPath}</string>
|
|
378
|
-
</array>
|
|
379
|
-
<key>RunAtLoad</key>
|
|
380
|
-
<true/>
|
|
381
|
-
<key>KeepAlive</key>
|
|
382
|
-
<true/>
|
|
383
|
-
<key>StandardOutPath</key>
|
|
384
|
-
<string>${logsDir}/daemon.log</string>
|
|
385
|
-
<key>StandardErrorPath</key>
|
|
386
|
-
<string>${logsDir}/daemon.log</string>
|
|
387
|
-
</dict>
|
|
388
|
-
</plist>`;
|
|
389
|
-
|
|
390
|
-
const plistPath = join(homedir(), "Library", "LaunchAgents", "com.opencode.browser-daemon.plist");
|
|
391
|
-
writeFileSync(plistPath, plist);
|
|
392
|
-
success(`Created launchd plist: ${plistPath}`);
|
|
393
|
-
|
|
394
|
-
try {
|
|
395
|
-
execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`);
|
|
396
|
-
execSync(`launchctl load "${plistPath}"`);
|
|
397
|
-
success("Daemon started");
|
|
398
|
-
} catch (e) {
|
|
399
|
-
error(`Failed to load daemon: ${e.message}`);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
log(`
|
|
403
|
-
${color("green", "✓")} Daemon installed and running
|
|
271
|
+
const lockFile = join(homedir(), ".opencode-browser", "lock.json");
|
|
404
272
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
273
|
+
if (!existsSync(lockFile)) {
|
|
274
|
+
success("Browser available (no lock file)");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
408
277
|
|
|
409
|
-
|
|
278
|
+
try {
|
|
279
|
+
const lock = JSON.parse(readFileSync(lockFile, "utf-8"));
|
|
280
|
+
log(`
|
|
281
|
+
Lock file: ${lockFile}
|
|
410
282
|
|
|
411
|
-
${
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
283
|
+
PID: ${lock.pid}
|
|
284
|
+
Session: ${lock.sessionId}
|
|
285
|
+
Started: ${lock.startedAt}
|
|
286
|
+
Working directory: ${lock.cwd}
|
|
415
287
|
`);
|
|
288
|
+
|
|
289
|
+
// Check if process is alive
|
|
290
|
+
try {
|
|
291
|
+
process.kill(lock.pid, 0);
|
|
292
|
+
warn(`Process ${lock.pid} is running. Browser is locked.`);
|
|
293
|
+
} catch {
|
|
294
|
+
success(`Process ${lock.pid} is dead. Lock is stale and will be auto-cleaned.`);
|
|
295
|
+
}
|
|
296
|
+
} catch (e) {
|
|
297
|
+
error(`Could not read lock file: ${e.message}`);
|
|
298
|
+
}
|
|
416
299
|
}
|
|
417
300
|
|
|
418
301
|
async function uninstall() {
|
|
419
302
|
header("Uninstalling OpenCode Browser");
|
|
420
303
|
|
|
304
|
+
// Remove old daemon
|
|
421
305
|
const os = platform();
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
306
|
+
if (os === "darwin") {
|
|
307
|
+
const plistPath = join(homedir(), "Library", "LaunchAgents", "com.opencode.browser-daemon.plist");
|
|
308
|
+
if (existsSync(plistPath)) {
|
|
309
|
+
try {
|
|
310
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: "ignore" });
|
|
311
|
+
unlinkSync(plistPath);
|
|
312
|
+
success("Removed daemon plist");
|
|
313
|
+
} catch {}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
425
316
|
|
|
426
|
-
|
|
317
|
+
// Remove native host registration (v1.x)
|
|
318
|
+
const nativeHostDir =
|
|
319
|
+
os === "darwin"
|
|
320
|
+
? join(homedir(), "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts")
|
|
321
|
+
: join(homedir(), ".config", "google-chrome", "NativeMessagingHosts");
|
|
427
322
|
|
|
323
|
+
const manifestPath = join(nativeHostDir, "com.opencode.browser_automation.json");
|
|
428
324
|
if (existsSync(manifestPath)) {
|
|
429
|
-
const { unlinkSync } = await import("fs");
|
|
430
325
|
unlinkSync(manifestPath);
|
|
431
326
|
success("Removed native host registration");
|
|
432
|
-
}
|
|
433
|
-
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Remove lock file
|
|
330
|
+
const lockFile = join(homedir(), ".opencode-browser", "lock.json");
|
|
331
|
+
if (existsSync(lockFile)) {
|
|
332
|
+
unlinkSync(lockFile);
|
|
333
|
+
success("Removed lock file");
|
|
434
334
|
}
|
|
435
335
|
|
|
436
336
|
log(`
|
|
437
|
-
${color("bright", "Note:")}
|
|
438
|
-
Remove
|
|
337
|
+
${color("bright", "Note:")} Extension files at ~/.opencode-browser/ were not removed.
|
|
338
|
+
Remove manually if needed:
|
|
439
339
|
rm -rf ~/.opencode-browser/
|
|
440
340
|
|
|
441
|
-
Also remove
|
|
341
|
+
Also remove "@different-ai/opencode-browser" from your opencode.json plugin array.
|
|
442
342
|
`);
|
|
443
343
|
}
|
|
444
344
|
|
package/extension/background.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const PLUGIN_URL = "ws://localhost:19222";
|
|
2
2
|
const KEEPALIVE_ALARM = "keepalive";
|
|
3
3
|
|
|
4
4
|
let ws = null;
|
|
@@ -23,10 +23,10 @@ function connect() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
ws = new WebSocket(
|
|
26
|
+
ws = new WebSocket(PLUGIN_URL);
|
|
27
27
|
|
|
28
28
|
ws.onopen = () => {
|
|
29
|
-
console.log("[OpenCode] Connected to
|
|
29
|
+
console.log("[OpenCode] Connected to plugin");
|
|
30
30
|
isConnected = true;
|
|
31
31
|
updateBadge(true);
|
|
32
32
|
};
|
package/extension/manifest.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "OpenCode Browser Automation",
|
|
4
|
-
"version": "
|
|
5
|
-
"description": "Browser automation for OpenCode
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"description": "Browser automation for OpenCode",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"tabs",
|
|
8
8
|
"activeTab",
|
|
9
9
|
"scripting",
|
|
10
|
-
"nativeMessaging",
|
|
11
10
|
"storage",
|
|
12
11
|
"notifications",
|
|
13
12
|
"alarms"
|
package/package.json
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@different-ai/opencode-browser",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Browser automation for OpenCode
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Browser automation plugin for OpenCode. Control your real Chrome browser with existing logins and cookies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"opencode-browser": "./bin/cli.js"
|
|
8
8
|
},
|
|
9
|
-
"main": "./src/
|
|
9
|
+
"main": "./src/plugin.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/plugin.ts",
|
|
12
|
+
"./plugin": "./src/plugin.ts"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"src",
|
|
17
|
+
"extension",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
10
20
|
"scripts": {
|
|
11
|
-
"start": "node src/server.js",
|
|
12
21
|
"install-extension": "node bin/cli.js install"
|
|
13
22
|
},
|
|
14
23
|
"keywords": [
|
|
@@ -16,8 +25,7 @@
|
|
|
16
25
|
"browser",
|
|
17
26
|
"automation",
|
|
18
27
|
"chrome",
|
|
19
|
-
"
|
|
20
|
-
"claude"
|
|
28
|
+
"plugin"
|
|
21
29
|
],
|
|
22
30
|
"author": "Benjamin Shafii",
|
|
23
31
|
"license": "MIT",
|
|
@@ -29,8 +37,11 @@
|
|
|
29
37
|
"url": "https://github.com/different-ai/opencode-browser/issues"
|
|
30
38
|
},
|
|
31
39
|
"homepage": "https://github.com/different-ai/opencode-browser#readme",
|
|
32
|
-
"
|
|
33
|
-
"@
|
|
34
|
-
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@opencode-ai/plugin": "*"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@opencode-ai/plugin": "*",
|
|
45
|
+
"bun-types": "*"
|
|
35
46
|
}
|
|
36
47
|
}
|