@different-ai/opencode-browser 4.2.5 → 4.3.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/.opencode/skill/browser-automation/SKILL.md +11 -5
- package/README.md +1 -0
- package/bin/cli.js +38 -4
- package/dist/plugin.js +11 -0
- package/extension/background.js +10 -0
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -17,11 +17,12 @@ metadata:
|
|
|
17
17
|
## Best-practice workflow
|
|
18
18
|
|
|
19
19
|
1. Inspect tabs with `browser_get_tabs`
|
|
20
|
-
2.
|
|
21
|
-
3.
|
|
22
|
-
4.
|
|
23
|
-
5.
|
|
24
|
-
6.
|
|
20
|
+
2. Open new tabs with `browser_open_tab` when needed
|
|
21
|
+
3. Navigate with `browser_navigate` if needed
|
|
22
|
+
4. Wait for UI using `browser_query` with `timeoutMs`
|
|
23
|
+
5. Discover candidates using `browser_query` with `mode=list`
|
|
24
|
+
6. Click or type using `index`
|
|
25
|
+
7. Confirm using `browser_query` or `browser_snapshot`
|
|
25
26
|
|
|
26
27
|
## Query modes
|
|
27
28
|
|
|
@@ -31,6 +32,11 @@ metadata:
|
|
|
31
32
|
- `exists`: check presence and count
|
|
32
33
|
- `page_text`: extract visible page text
|
|
33
34
|
|
|
35
|
+
## Opening tabs
|
|
36
|
+
|
|
37
|
+
- Use `browser_open_tab` to create a new tab, optionally with `url` and `active`
|
|
38
|
+
- Example: `browser_open_tab({ url: "https://example.com", active: false })`
|
|
39
|
+
|
|
34
40
|
## Troubleshooting
|
|
35
41
|
|
|
36
42
|
- If a selector fails, run `browser_query` with `mode=page_text` to confirm the content exists
|
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ OpenCode Plugin <-> Local Broker (unix socket) <-> Native Host <-> Chrome Extens
|
|
|
57
57
|
Core primitives:
|
|
58
58
|
- `browser_status`
|
|
59
59
|
- `browser_get_tabs`
|
|
60
|
+
- `browser_open_tab`
|
|
60
61
|
- `browser_navigate`
|
|
61
62
|
- `browser_query` (modes: `text`, `value`, `list`, `exists`, `page_text`; optional `timeoutMs`/`pollMs`)
|
|
62
63
|
- `browser_click`
|
package/bin/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { join, dirname } from "path";
|
|
|
26
26
|
import { fileURLToPath } from "url";
|
|
27
27
|
import { createInterface } from "readline";
|
|
28
28
|
import { createConnection } from "net";
|
|
29
|
+
import { execSync } from "child_process";
|
|
29
30
|
|
|
30
31
|
const __filename = fileURLToPath(import.meta.url);
|
|
31
32
|
const __dirname = dirname(__filename);
|
|
@@ -35,6 +36,7 @@ const BASE_DIR = join(homedir(), ".opencode-browser");
|
|
|
35
36
|
const EXTENSION_DIR = join(BASE_DIR, "extension");
|
|
36
37
|
const BROKER_DST = join(BASE_DIR, "broker.cjs");
|
|
37
38
|
const NATIVE_HOST_DST = join(BASE_DIR, "native-host.cjs");
|
|
39
|
+
const NATIVE_HOST_WRAPPER = join(BASE_DIR, "host-wrapper.sh");
|
|
38
40
|
const CONFIG_DST = join(BASE_DIR, "config.json");
|
|
39
41
|
const BROKER_SOCKET = join(BASE_DIR, "broker.sock");
|
|
40
42
|
|
|
@@ -94,6 +96,26 @@ function ensureDir(p) {
|
|
|
94
96
|
mkdirSync(p, { recursive: true });
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
function resolveNodePath() {
|
|
100
|
+
if (process.env.OPENCODE_BROWSER_NODE) return process.env.OPENCODE_BROWSER_NODE;
|
|
101
|
+
if (process.execPath && /node(\.exe)?$/.test(process.execPath)) return process.execPath;
|
|
102
|
+
try {
|
|
103
|
+
const output = execSync("which node", { stdio: ["ignore", "pipe", "ignore"] })
|
|
104
|
+
.toString("utf8")
|
|
105
|
+
.trim();
|
|
106
|
+
if (output) return output;
|
|
107
|
+
} catch {}
|
|
108
|
+
return process.execPath;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function writeHostWrapper(nodePath) {
|
|
112
|
+
ensureDir(BASE_DIR);
|
|
113
|
+
const script = `#!/bin/sh\n"${nodePath}" "${NATIVE_HOST_DST}"\n`;
|
|
114
|
+
writeFileSync(NATIVE_HOST_WRAPPER, script, { mode: 0o755 });
|
|
115
|
+
chmodSync(NATIVE_HOST_WRAPPER, 0o755);
|
|
116
|
+
return NATIVE_HOST_WRAPPER;
|
|
117
|
+
}
|
|
118
|
+
|
|
97
119
|
function createJsonLineParser(onMessage) {
|
|
98
120
|
let buffer = "";
|
|
99
121
|
return (chunk) => {
|
|
@@ -196,13 +218,13 @@ function nativeHostManifestPath(dir) {
|
|
|
196
218
|
return join(dir, `${NATIVE_HOST_NAME}.json`);
|
|
197
219
|
}
|
|
198
220
|
|
|
199
|
-
function writeNativeHostManifest(dir, extensionId) {
|
|
221
|
+
function writeNativeHostManifest(dir, extensionId, hostPath) {
|
|
200
222
|
ensureDir(dir);
|
|
201
223
|
|
|
202
224
|
const manifest = {
|
|
203
225
|
name: NATIVE_HOST_NAME,
|
|
204
226
|
description: "OpenCode Browser native messaging host",
|
|
205
|
-
path: NATIVE_HOST_DST,
|
|
227
|
+
path: hostPath || NATIVE_HOST_DST,
|
|
206
228
|
type: "stdio",
|
|
207
229
|
allowed_origins: [`chrome-extension://${extensionId}/`],
|
|
208
230
|
};
|
|
@@ -323,14 +345,21 @@ Find it at ${color("cyan", "chrome://extensions")}:
|
|
|
323
345
|
success(`Installed broker: ${BROKER_DST}`);
|
|
324
346
|
success(`Installed native host: ${NATIVE_HOST_DST}`);
|
|
325
347
|
|
|
326
|
-
|
|
348
|
+
const nodePath = resolveNodePath();
|
|
349
|
+
if (!/node(\.exe)?$/.test(nodePath)) {
|
|
350
|
+
warn(`Node not detected; using ${nodePath}. Set OPENCODE_BROWSER_NODE if needed.`);
|
|
351
|
+
}
|
|
352
|
+
const hostPath = writeHostWrapper(nodePath);
|
|
353
|
+
success(`Installed host wrapper: ${hostPath}`);
|
|
354
|
+
|
|
355
|
+
saveConfig({ extensionId, installedAt: new Date().toISOString(), nodePath });
|
|
327
356
|
|
|
328
357
|
header("Step 6: Register Native Messaging Host");
|
|
329
358
|
|
|
330
359
|
const hostDirs = getNativeHostDirs(osName);
|
|
331
360
|
for (const dir of hostDirs) {
|
|
332
361
|
try {
|
|
333
|
-
writeNativeHostManifest(dir, extensionId);
|
|
362
|
+
writeNativeHostManifest(dir, extensionId, hostPath);
|
|
334
363
|
success(`Wrote native host manifest: ${nativeHostManifestPath(dir)}`);
|
|
335
364
|
} catch (e) {
|
|
336
365
|
warn(`Could not write native host manifest to: ${dir}`);
|
|
@@ -520,6 +549,7 @@ async function status() {
|
|
|
520
549
|
success(`Extension dir present: ${existsSync(EXTENSION_DIR)}`);
|
|
521
550
|
success(`Broker installed: ${existsSync(BROKER_DST)}`);
|
|
522
551
|
success(`Native host installed: ${existsSync(NATIVE_HOST_DST)}`);
|
|
552
|
+
success(`Host wrapper installed: ${existsSync(NATIVE_HOST_WRAPPER)}`);
|
|
523
553
|
|
|
524
554
|
const cfg = loadConfig();
|
|
525
555
|
if (cfg?.extensionId) {
|
|
@@ -528,6 +558,10 @@ async function status() {
|
|
|
528
558
|
warn("No config.json found (run install)");
|
|
529
559
|
}
|
|
530
560
|
|
|
561
|
+
if (cfg?.nodePath) {
|
|
562
|
+
success(`Node path: ${cfg.nodePath}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
531
565
|
const osName = platform();
|
|
532
566
|
const hostDirs = getNativeHostDirs(osName);
|
|
533
567
|
let foundAny = false;
|
package/dist/plugin.js
CHANGED
|
@@ -12514,6 +12514,17 @@ var plugin = async (ctx) => {
|
|
|
12514
12514
|
return toolResultText(data, "ok");
|
|
12515
12515
|
}
|
|
12516
12516
|
}),
|
|
12517
|
+
browser_open_tab: tool({
|
|
12518
|
+
description: "Open a new browser tab",
|
|
12519
|
+
args: {
|
|
12520
|
+
url: schema.string().optional(),
|
|
12521
|
+
active: schema.boolean().optional()
|
|
12522
|
+
},
|
|
12523
|
+
async execute({ url: url2, active }, ctx2) {
|
|
12524
|
+
const data = await brokerRequest("tool", { tool: "open_tab", args: { url: url2, active } });
|
|
12525
|
+
return toolResultText(data, "Opened new tab");
|
|
12526
|
+
}
|
|
12527
|
+
}),
|
|
12517
12528
|
browser_navigate: tool({
|
|
12518
12529
|
description: "Navigate to a URL in the browser",
|
|
12519
12530
|
args: {
|
package/extension/background.js
CHANGED
|
@@ -100,6 +100,7 @@ async function executeTool(toolName, args) {
|
|
|
100
100
|
const tools = {
|
|
101
101
|
get_active_tab: toolGetActiveTab,
|
|
102
102
|
get_tabs: toolGetTabs,
|
|
103
|
+
open_tab: toolOpenTab,
|
|
103
104
|
navigate: toolNavigate,
|
|
104
105
|
click: toolClick,
|
|
105
106
|
type: toolType,
|
|
@@ -487,6 +488,15 @@ async function toolGetActiveTab() {
|
|
|
487
488
|
return { tabId: tab.id, content: { tabId: tab.id, url: tab.url, title: tab.title } }
|
|
488
489
|
}
|
|
489
490
|
|
|
491
|
+
async function toolOpenTab({ url, active = true }) {
|
|
492
|
+
const createOptions = {}
|
|
493
|
+
if (typeof url === "string" && url.trim()) createOptions.url = url.trim()
|
|
494
|
+
if (typeof active === "boolean") createOptions.active = active
|
|
495
|
+
|
|
496
|
+
const tab = await chrome.tabs.create(createOptions)
|
|
497
|
+
return { tabId: tab.id, content: { tabId: tab.id, url: tab.url, active: tab.active } }
|
|
498
|
+
}
|
|
499
|
+
|
|
490
500
|
async function toolNavigate({ url, tabId }) {
|
|
491
501
|
if (!url) throw new Error("URL is required")
|
|
492
502
|
const tab = await getTabById(tabId)
|
package/extension/manifest.json
CHANGED