@different-ai/opencode-browser 3.0.0 → 4.0.0
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 +45 -102
- package/bin/broker.cjs +290 -0
- package/bin/cli.js +236 -237
- package/bin/native-host.cjs +136 -0
- package/extension/background.js +235 -190
- package/extension/manifest.json +3 -2
- package/package.json +14 -12
- package/src/plugin.ts +275 -0
- package/src/mcp-server.ts +0 -440
package/bin/cli.js
CHANGED
|
@@ -2,30 +2,48 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* OpenCode Browser - CLI
|
|
4
4
|
*
|
|
5
|
+
* Architecture (v4):
|
|
6
|
+
* OpenCode Plugin <-> Local Broker (unix socket) <-> Native Messaging Host <-> Chrome Extension
|
|
7
|
+
*
|
|
5
8
|
* Commands:
|
|
6
|
-
* install
|
|
7
|
-
*
|
|
8
|
-
* status
|
|
9
|
+
* install - Install extension + native host
|
|
10
|
+
* uninstall - Remove native host registration
|
|
11
|
+
* status - Show installation status
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
copyFileSync,
|
|
20
|
+
readdirSync,
|
|
21
|
+
unlinkSync,
|
|
22
|
+
chmodSync,
|
|
23
|
+
} from "fs";
|
|
12
24
|
import { homedir, platform } from "os";
|
|
13
25
|
import { join, dirname } from "path";
|
|
14
26
|
import { fileURLToPath } from "url";
|
|
15
|
-
import { execSync, spawn } from "child_process";
|
|
16
27
|
import { createInterface } from "readline";
|
|
17
28
|
|
|
18
29
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
30
|
const __dirname = dirname(__filename);
|
|
20
31
|
const PACKAGE_ROOT = join(__dirname, "..");
|
|
21
32
|
|
|
33
|
+
const BASE_DIR = join(homedir(), ".opencode-browser");
|
|
34
|
+
const EXTENSION_DIR = join(BASE_DIR, "extension");
|
|
35
|
+
const BROKER_DST = join(BASE_DIR, "broker.cjs");
|
|
36
|
+
const NATIVE_HOST_DST = join(BASE_DIR, "native-host.cjs");
|
|
37
|
+
const CONFIG_DST = join(BASE_DIR, "config.json");
|
|
38
|
+
|
|
39
|
+
const NATIVE_HOST_NAME = "com.opencode.browser_automation";
|
|
40
|
+
|
|
22
41
|
const COLORS = {
|
|
23
42
|
reset: "\x1b[0m",
|
|
24
43
|
bright: "\x1b[1m",
|
|
25
44
|
red: "\x1b[31m",
|
|
26
45
|
green: "\x1b[32m",
|
|
27
46
|
yellow: "\x1b[33m",
|
|
28
|
-
blue: "\x1b[34m",
|
|
29
47
|
cyan: "\x1b[36m",
|
|
30
48
|
};
|
|
31
49
|
|
|
@@ -61,9 +79,7 @@ const rl = createInterface({
|
|
|
61
79
|
|
|
62
80
|
function ask(question) {
|
|
63
81
|
return new Promise((resolve) => {
|
|
64
|
-
rl.question(question, (answer) =>
|
|
65
|
-
resolve(answer.trim());
|
|
66
|
-
});
|
|
82
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
67
83
|
});
|
|
68
84
|
}
|
|
69
85
|
|
|
@@ -72,323 +88,306 @@ async function confirm(question) {
|
|
|
72
88
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
73
89
|
}
|
|
74
90
|
|
|
91
|
+
function ensureDir(p) {
|
|
92
|
+
mkdirSync(p, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function copyDirRecursive(srcDir, destDir) {
|
|
96
|
+
ensureDir(destDir);
|
|
97
|
+
const entries = readdirSync(srcDir, { recursive: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const srcPath = join(srcDir, entry);
|
|
100
|
+
const destPath = join(destDir, entry);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
readdirSync(srcPath);
|
|
104
|
+
ensureDir(destPath);
|
|
105
|
+
} catch {
|
|
106
|
+
ensureDir(dirname(destPath));
|
|
107
|
+
copyFileSync(srcPath, destPath);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getNativeHostDirs(osName) {
|
|
113
|
+
if (osName === "darwin") {
|
|
114
|
+
const base = join(homedir(), "Library", "Application Support");
|
|
115
|
+
return [
|
|
116
|
+
join(base, "Google", "Chrome", "NativeMessagingHosts"),
|
|
117
|
+
join(base, "Chromium", "NativeMessagingHosts"),
|
|
118
|
+
join(base, "BraveSoftware", "Brave-Browser", "NativeMessagingHosts"),
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// linux
|
|
123
|
+
const base = join(homedir(), ".config");
|
|
124
|
+
return [
|
|
125
|
+
join(base, "google-chrome", "NativeMessagingHosts"),
|
|
126
|
+
join(base, "chromium", "NativeMessagingHosts"),
|
|
127
|
+
join(base, "BraveSoftware", "Brave-Browser", "NativeMessagingHosts"),
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function nativeHostManifestPath(dir) {
|
|
132
|
+
return join(dir, `${NATIVE_HOST_NAME}.json`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function writeNativeHostManifest(dir, extensionId) {
|
|
136
|
+
ensureDir(dir);
|
|
137
|
+
|
|
138
|
+
const manifest = {
|
|
139
|
+
name: NATIVE_HOST_NAME,
|
|
140
|
+
description: "OpenCode Browser native messaging host",
|
|
141
|
+
path: NATIVE_HOST_DST,
|
|
142
|
+
type: "stdio",
|
|
143
|
+
allowed_origins: [`chrome-extension://${extensionId}/`],
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
writeFileSync(nativeHostManifestPath(dir), JSON.stringify(manifest, null, 2) + "\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function loadConfig() {
|
|
150
|
+
try {
|
|
151
|
+
if (!existsSync(CONFIG_DST)) return null;
|
|
152
|
+
return JSON.parse(readFileSync(CONFIG_DST, "utf-8"));
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function saveConfig(config) {
|
|
159
|
+
ensureDir(BASE_DIR);
|
|
160
|
+
writeFileSync(CONFIG_DST, JSON.stringify(config, null, 2) + "\n");
|
|
161
|
+
}
|
|
162
|
+
|
|
75
163
|
async function main() {
|
|
76
164
|
const command = process.argv[2];
|
|
77
165
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
166
|
+
console.log(`
|
|
167
|
+
${color("cyan", color("bright", "OpenCode Browser v4"))}
|
|
168
|
+
${color("cyan", "Browser automation plugin (native messaging + per-tab ownership)")}
|
|
169
|
+
`);
|
|
170
|
+
|
|
171
|
+
if (command === "install") {
|
|
83
172
|
await install();
|
|
84
|
-
rl.close();
|
|
85
173
|
} else if (command === "uninstall") {
|
|
86
|
-
await showHeader();
|
|
87
174
|
await uninstall();
|
|
88
|
-
rl.close();
|
|
89
175
|
} else if (command === "status") {
|
|
90
|
-
await showHeader();
|
|
91
176
|
await status();
|
|
92
|
-
rl.close();
|
|
93
177
|
} else {
|
|
94
|
-
await showHeader();
|
|
95
178
|
log(`
|
|
96
179
|
${color("bright", "Usage:")}
|
|
97
|
-
npx @different-ai/opencode-browser install
|
|
98
|
-
npx @different-ai/opencode-browser
|
|
99
|
-
npx @different-ai/opencode-browser
|
|
100
|
-
npx @different-ai/opencode-browser serve Run MCP server (internal)
|
|
180
|
+
npx @different-ai/opencode-browser install
|
|
181
|
+
npx @different-ai/opencode-browser status
|
|
182
|
+
npx @different-ai/opencode-browser uninstall
|
|
101
183
|
|
|
102
184
|
${color("bright", "Quick Start:")}
|
|
103
185
|
1. Run: npx @different-ai/opencode-browser install
|
|
104
|
-
2.
|
|
105
|
-
|
|
106
|
-
3. Restart OpenCode
|
|
186
|
+
2. Restart OpenCode
|
|
187
|
+
3. Use: browser_navigate / browser_click / browser_snapshot
|
|
107
188
|
`);
|
|
108
|
-
rl.close();
|
|
109
189
|
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function showHeader() {
|
|
113
|
-
console.log(`
|
|
114
|
-
${color("cyan", color("bright", "OpenCode Browser v2.1"))}
|
|
115
|
-
${color("cyan", "Browser automation MCP server for OpenCode")}
|
|
116
|
-
`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function serve() {
|
|
120
|
-
// Launch the MCP server
|
|
121
|
-
const serverPath = join(PACKAGE_ROOT, "src", "mcp-server.ts");
|
|
122
|
-
|
|
123
|
-
// Use bun to run the TypeScript server
|
|
124
|
-
const child = spawn("bun", ["run", serverPath], {
|
|
125
|
-
stdio: "inherit",
|
|
126
|
-
env: process.env,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
child.on("error", (err) => {
|
|
130
|
-
console.error("[browser-mcp] Failed to start server:", err);
|
|
131
|
-
process.exit(1);
|
|
132
|
-
});
|
|
133
190
|
|
|
134
|
-
|
|
135
|
-
process.exit(code || 0);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Forward signals to child
|
|
139
|
-
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
140
|
-
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
191
|
+
rl.close();
|
|
141
192
|
}
|
|
142
193
|
|
|
143
194
|
async function install() {
|
|
144
195
|
header("Step 1: Check Platform");
|
|
145
196
|
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
error(`Unsupported platform: ${
|
|
197
|
+
const osName = platform();
|
|
198
|
+
if (osName !== "darwin" && osName !== "linux") {
|
|
199
|
+
error(`Unsupported platform: ${osName}`);
|
|
149
200
|
error("OpenCode Browser currently supports macOS and Linux only.");
|
|
150
201
|
process.exit(1);
|
|
151
202
|
}
|
|
152
|
-
success(`Platform: ${
|
|
203
|
+
success(`Platform: ${osName === "darwin" ? "macOS" : "Linux"}`);
|
|
153
204
|
|
|
154
205
|
header("Step 2: Copy Extension Files");
|
|
155
206
|
|
|
156
|
-
|
|
207
|
+
ensureDir(BASE_DIR);
|
|
157
208
|
const srcExtensionDir = join(PACKAGE_ROOT, "extension");
|
|
209
|
+
copyDirRecursive(srcExtensionDir, EXTENSION_DIR);
|
|
210
|
+
success(`Extension files copied to: ${EXTENSION_DIR}`);
|
|
158
211
|
|
|
159
|
-
|
|
212
|
+
header("Step 3: Load & Pin Extension");
|
|
160
213
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const srcPath = join(srcExtensionDir, file);
|
|
164
|
-
const destPath = join(extensionDir, file);
|
|
214
|
+
log(`
|
|
215
|
+
To load the extension:
|
|
165
216
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
217
|
+
1. Open ${color("cyan", "chrome://extensions")}
|
|
218
|
+
2. Enable ${color("bright", "Developer mode")}
|
|
219
|
+
3. Click ${color("bright", "Load unpacked")}
|
|
220
|
+
4. Select:
|
|
221
|
+
${color("cyan", EXTENSION_DIR)}
|
|
222
|
+
|
|
223
|
+
After loading, ${color("bright", "pin the extension")}: open the Extensions menu (puzzle icon) and click the pin.
|
|
224
|
+
`);
|
|
174
225
|
|
|
175
|
-
|
|
226
|
+
await ask(color("bright", "Press Enter when you've loaded and pinned the extension..."));
|
|
176
227
|
|
|
177
|
-
header("Step
|
|
228
|
+
header("Step 4: Get Extension ID");
|
|
178
229
|
|
|
179
230
|
log(`
|
|
180
|
-
|
|
231
|
+
We need the extension ID to register the native messaging host.
|
|
181
232
|
|
|
182
|
-
|
|
233
|
+
Find it at ${color("cyan", "chrome://extensions")}:
|
|
234
|
+
- Locate ${color("bright", "OpenCode Browser Automation")}
|
|
235
|
+
- Click ${color("bright", "Details")}
|
|
236
|
+
- Copy the ${color("bright", "ID")}
|
|
237
|
+
`);
|
|
183
238
|
|
|
184
|
-
|
|
185
|
-
|
|
239
|
+
const extensionId = await ask(color("bright", "Paste Extension ID: "));
|
|
240
|
+
if (!/^[a-p]{32}$/i.test(extensionId)) {
|
|
241
|
+
warn("That doesn't look like a Chrome extension ID (expected 32 chars a-p). Continuing anyway.");
|
|
242
|
+
}
|
|
186
243
|
|
|
187
|
-
|
|
244
|
+
header("Step 5: Install Local Host + Broker");
|
|
188
245
|
|
|
189
|
-
|
|
246
|
+
const brokerSrc = join(PACKAGE_ROOT, "bin", "broker.cjs");
|
|
247
|
+
const nativeHostSrc = join(PACKAGE_ROOT, "bin", "native-host.cjs");
|
|
190
248
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
${os === "darwin" ? color("yellow", "Tip: Press Cmd+Shift+G and paste the path above") : ""}
|
|
194
|
-
`);
|
|
249
|
+
copyFileSync(brokerSrc, BROKER_DST);
|
|
250
|
+
copyFileSync(nativeHostSrc, NATIVE_HOST_DST);
|
|
195
251
|
|
|
196
|
-
|
|
252
|
+
try {
|
|
253
|
+
chmodSync(BROKER_DST, 0o755);
|
|
254
|
+
} catch {}
|
|
255
|
+
try {
|
|
256
|
+
chmodSync(NATIVE_HOST_DST, 0o755);
|
|
257
|
+
} catch {}
|
|
197
258
|
|
|
198
|
-
|
|
259
|
+
success(`Installed broker: ${BROKER_DST}`);
|
|
260
|
+
success(`Installed native host: ${NATIVE_HOST_DST}`);
|
|
199
261
|
|
|
200
|
-
|
|
201
|
-
browser: {
|
|
202
|
-
type: "local",
|
|
203
|
-
command: ["bunx", "@different-ai/opencode-browser", "serve"],
|
|
204
|
-
},
|
|
205
|
-
};
|
|
262
|
+
saveConfig({ extensionId, installedAt: new Date().toISOString() });
|
|
206
263
|
|
|
207
|
-
|
|
208
|
-
Add the MCP server to your ${color("cyan", "opencode.json")}:
|
|
264
|
+
header("Step 6: Register Native Messaging Host");
|
|
209
265
|
|
|
210
|
-
|
|
266
|
+
const hostDirs = getNativeHostDirs(osName);
|
|
267
|
+
for (const dir of hostDirs) {
|
|
268
|
+
try {
|
|
269
|
+
writeNativeHostManifest(dir, extensionId);
|
|
270
|
+
success(`Wrote native host manifest: ${nativeHostManifestPath(dir)}`);
|
|
271
|
+
} catch (e) {
|
|
272
|
+
warn(`Could not write native host manifest to: ${dir}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
211
275
|
|
|
212
|
-
|
|
213
|
-
${color("bright", JSON.stringify({ mcp: mcpConfig }, null, 2))}
|
|
214
|
-
`);
|
|
276
|
+
header("Step 7: Configure OpenCode");
|
|
215
277
|
|
|
216
278
|
const opencodeJsonPath = join(process.cwd(), "opencode.json");
|
|
217
279
|
|
|
218
|
-
|
|
219
|
-
const shouldUpdate = await confirm(`Found opencode.json. Add MCP server automatically?`);
|
|
280
|
+
const desiredPlugin = "@different-ai/opencode-browser";
|
|
220
281
|
|
|
282
|
+
if (existsSync(opencodeJsonPath)) {
|
|
283
|
+
const shouldUpdate = await confirm("Found opencode.json. Add plugin automatically?");
|
|
221
284
|
if (shouldUpdate) {
|
|
222
285
|
try {
|
|
223
286
|
const config = JSON.parse(readFileSync(opencodeJsonPath, "utf-8"));
|
|
224
|
-
config.
|
|
225
|
-
config.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
if (config.plugin.length === 0) {
|
|
235
|
-
delete config.plugin;
|
|
236
|
-
}
|
|
287
|
+
config.plugin = config.plugin || [];
|
|
288
|
+
if (!Array.isArray(config.plugin)) config.plugin = [];
|
|
289
|
+
if (!config.plugin.includes(desiredPlugin)) config.plugin.push(desiredPlugin);
|
|
290
|
+
|
|
291
|
+
// Remove MCP config if present
|
|
292
|
+
if (config.mcp?.browser) {
|
|
293
|
+
delete config.mcp.browser;
|
|
294
|
+
if (Object.keys(config.mcp).length === 0) delete config.mcp;
|
|
295
|
+
warn("Removed old MCP browser config (replaced by plugin)");
|
|
237
296
|
}
|
|
238
|
-
|
|
297
|
+
|
|
239
298
|
writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
240
|
-
success("Updated opencode.json with
|
|
299
|
+
success("Updated opencode.json with plugin");
|
|
241
300
|
} catch (e) {
|
|
242
301
|
error(`Failed to update opencode.json: ${e.message}`);
|
|
243
|
-
log("Please add the MCP config manually.");
|
|
244
302
|
}
|
|
245
303
|
}
|
|
246
304
|
} else {
|
|
247
|
-
const shouldCreate = await confirm(
|
|
248
|
-
|
|
305
|
+
const shouldCreate = await confirm("No opencode.json found. Create one?");
|
|
249
306
|
if (shouldCreate) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
mcp: mcpConfig,
|
|
254
|
-
};
|
|
255
|
-
writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
256
|
-
success("Created opencode.json with MCP server");
|
|
257
|
-
} catch (e) {
|
|
258
|
-
error(`Failed to create opencode.json: ${e.message}`);
|
|
259
|
-
}
|
|
307
|
+
const config = { $schema: "https://opencode.ai/config.json", plugin: [desiredPlugin] };
|
|
308
|
+
writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
309
|
+
success("Created opencode.json with plugin");
|
|
260
310
|
}
|
|
261
311
|
}
|
|
262
312
|
|
|
263
|
-
// Clean up old daemon/plugin if present
|
|
264
|
-
header("Step 5: Cleanup (migration)");
|
|
265
|
-
|
|
266
|
-
const oldDaemonPlist = join(homedir(), "Library", "LaunchAgents", "com.opencode.browser-daemon.plist");
|
|
267
|
-
if (existsSync(oldDaemonPlist)) {
|
|
268
|
-
try {
|
|
269
|
-
execSync(`launchctl unload "${oldDaemonPlist}" 2>/dev/null || true`, { stdio: "ignore" });
|
|
270
|
-
unlinkSync(oldDaemonPlist);
|
|
271
|
-
success("Removed old daemon (no longer needed)");
|
|
272
|
-
} catch {
|
|
273
|
-
warn("Could not remove old daemon plist. Remove manually if needed.");
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Remove old lock file
|
|
278
|
-
const oldLockFile = join(homedir(), ".opencode-browser", "lock.json");
|
|
279
|
-
if (existsSync(oldLockFile)) {
|
|
280
|
-
try {
|
|
281
|
-
unlinkSync(oldLockFile);
|
|
282
|
-
success("Removed old lock file (not needed with MCP)");
|
|
283
|
-
} catch {}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
success("Cleanup complete");
|
|
287
|
-
|
|
288
313
|
header("Installation Complete!");
|
|
289
314
|
|
|
290
315
|
log(`
|
|
291
|
-
${color("
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
${color("bright", "
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
4. Browser tools are available to any OpenCode session!
|
|
299
|
-
|
|
300
|
-
${color("bright", "Available tools:")}
|
|
301
|
-
browser_status - Check if browser is connected
|
|
302
|
-
browser_navigate - Go to a URL
|
|
303
|
-
browser_click - Click an element
|
|
304
|
-
browser_type - Type into an input
|
|
305
|
-
browser_screenshot - Capture the page
|
|
306
|
-
browser_snapshot - Get accessibility tree + all links
|
|
307
|
-
browser_get_tabs - List open tabs
|
|
308
|
-
browser_scroll - Scroll the page
|
|
309
|
-
browser_wait - Wait for duration
|
|
310
|
-
browser_execute - Run JavaScript
|
|
311
|
-
|
|
312
|
-
${color("bright", "Benefits of MCP architecture:")}
|
|
313
|
-
- No session conflicts between OpenCode instances
|
|
314
|
-
- Server runs independently of OpenCode process
|
|
315
|
-
- Clean separation of concerns
|
|
316
|
-
|
|
317
|
-
${color("bright", "Test it:")}
|
|
318
|
-
Restart OpenCode and try: ${color("cyan", '"Check browser status"')}
|
|
316
|
+
${color("bright", "What happens now:")}
|
|
317
|
+
- The extension connects to the native host automatically.
|
|
318
|
+
- OpenCode loads the plugin, which talks to the broker.
|
|
319
|
+
- The broker enforces ${color("bright", "per-tab ownership")}. First touch auto-claims.
|
|
320
|
+
|
|
321
|
+
${color("bright", "Try it:")}
|
|
322
|
+
Restart OpenCode and run: ${color("cyan", "browser_get_tabs")}
|
|
319
323
|
`);
|
|
320
324
|
}
|
|
321
325
|
|
|
322
326
|
async function status() {
|
|
323
|
-
header("
|
|
327
|
+
header("Status");
|
|
324
328
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
success("WebSocket server is running on port 19222");
|
|
330
|
-
log(result);
|
|
331
|
-
} else {
|
|
332
|
-
warn("WebSocket server not running (starts on demand via MCP)");
|
|
333
|
-
}
|
|
334
|
-
} catch {
|
|
335
|
-
warn("Could not check port status");
|
|
336
|
-
}
|
|
329
|
+
success(`Base dir: ${BASE_DIR}`);
|
|
330
|
+
success(`Extension dir present: ${existsSync(EXTENSION_DIR)}`);
|
|
331
|
+
success(`Broker installed: ${existsSync(BROKER_DST)}`);
|
|
332
|
+
success(`Native host installed: ${existsSync(NATIVE_HOST_DST)}`);
|
|
337
333
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
success(`Extension installed at: ${extensionDir}`);
|
|
334
|
+
const cfg = loadConfig();
|
|
335
|
+
if (cfg?.extensionId) {
|
|
336
|
+
success(`Configured extension ID: ${cfg.extensionId}`);
|
|
342
337
|
} else {
|
|
343
|
-
warn("
|
|
338
|
+
warn("No config.json found (run install)");
|
|
344
339
|
}
|
|
345
|
-
}
|
|
346
340
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
try {
|
|
356
|
-
execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: "ignore" });
|
|
357
|
-
unlinkSync(plistPath);
|
|
358
|
-
success("Removed daemon plist");
|
|
359
|
-
} catch {}
|
|
341
|
+
const osName = platform();
|
|
342
|
+
const hostDirs = getNativeHostDirs(osName);
|
|
343
|
+
let foundAny = false;
|
|
344
|
+
for (const dir of hostDirs) {
|
|
345
|
+
const p = nativeHostManifestPath(dir);
|
|
346
|
+
if (existsSync(p)) {
|
|
347
|
+
foundAny = true;
|
|
348
|
+
success(`Native host manifest: ${p}`);
|
|
360
349
|
}
|
|
361
350
|
}
|
|
351
|
+
if (!foundAny) {
|
|
352
|
+
warn("No native host manifest found. Run: npx @different-ai/opencode-browser install");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
362
355
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
os === "darwin"
|
|
366
|
-
? join(homedir(), "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts")
|
|
367
|
-
: join(homedir(), ".config", "google-chrome", "NativeMessagingHosts");
|
|
356
|
+
async function uninstall() {
|
|
357
|
+
header("Uninstall");
|
|
368
358
|
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
359
|
+
const osName = platform();
|
|
360
|
+
const hostDirs = getNativeHostDirs(osName);
|
|
361
|
+
for (const dir of hostDirs) {
|
|
362
|
+
const p = nativeHostManifestPath(dir);
|
|
363
|
+
if (!existsSync(p)) continue;
|
|
364
|
+
try {
|
|
365
|
+
unlinkSync(p);
|
|
366
|
+
success(`Removed native host manifest: ${p}`);
|
|
367
|
+
} catch {
|
|
368
|
+
warn(`Could not remove: ${p}`);
|
|
369
|
+
}
|
|
373
370
|
}
|
|
374
371
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
372
|
+
for (const p of [BROKER_DST, NATIVE_HOST_DST, CONFIG_DST, join(BASE_DIR, "broker.sock")]) {
|
|
373
|
+
if (!existsSync(p)) continue;
|
|
374
|
+
try {
|
|
375
|
+
unlinkSync(p);
|
|
376
|
+
success(`Removed: ${p}`);
|
|
377
|
+
} catch {
|
|
378
|
+
// ignore
|
|
379
|
+
}
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
log(`
|
|
383
|
-
${color("bright", "Note:")}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
Also remove the "browser" entry from your opencode.json mcp section.
|
|
383
|
+
${color("bright", "Note:")}
|
|
384
|
+
- The unpacked extension folder remains at: ${EXTENSION_DIR}
|
|
385
|
+
- Remove it manually in ${color("cyan", "chrome://extensions")}
|
|
386
|
+
- Remove ${color("bright", "@different-ai/opencode-browser")} from your opencode.json plugin list if desired.
|
|
388
387
|
`);
|
|
389
388
|
}
|
|
390
389
|
|
|
391
390
|
main().catch((e) => {
|
|
392
|
-
error(e.message);
|
|
391
|
+
error(e.message || String(e));
|
|
393
392
|
process.exit(1);
|
|
394
393
|
});
|