@different-ai/opencode-browser 4.2.2 → 4.2.4
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 +38 -0
- package/bin/cli.js +112 -42
- package/extension/manifest.json +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-automation
|
|
3
|
+
description: Reliable, composable browser automation using minimal OpenCode Browser primitives.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: opencode
|
|
6
|
+
metadata:
|
|
7
|
+
audience: agents
|
|
8
|
+
domain: browser
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What I do
|
|
12
|
+
|
|
13
|
+
- Provide a safe, composable workflow for browsing tasks
|
|
14
|
+
- Use `browser_query` list and index selection to click reliably
|
|
15
|
+
- Confirm state changes after each action
|
|
16
|
+
|
|
17
|
+
## Best-practice workflow
|
|
18
|
+
|
|
19
|
+
1. Inspect tabs with `browser_get_tabs`
|
|
20
|
+
2. Navigate with `browser_navigate` if needed
|
|
21
|
+
3. Wait for UI using `browser_query` with `timeoutMs`
|
|
22
|
+
4. Discover candidates using `browser_query` with `mode=list`
|
|
23
|
+
5. Click or type using `index`
|
|
24
|
+
6. Confirm using `browser_query` or `browser_snapshot`
|
|
25
|
+
|
|
26
|
+
## Query modes
|
|
27
|
+
|
|
28
|
+
- `text`: read visible text from a matched element
|
|
29
|
+
- `value`: read input values
|
|
30
|
+
- `list`: list many matches with text/metadata
|
|
31
|
+
- `exists`: check presence and count
|
|
32
|
+
- `page_text`: extract visible page text
|
|
33
|
+
|
|
34
|
+
## Troubleshooting
|
|
35
|
+
|
|
36
|
+
- If a selector fails, run `browser_query` with `mode=page_text` to confirm the content exists
|
|
37
|
+
- Use `mode=list` on broad selectors (`button`, `a`, `*[role="button"]`) and choose by index
|
|
38
|
+
- Confirm results after each action
|
package/bin/cli.js
CHANGED
|
@@ -275,13 +275,6 @@ Find it at ${color("cyan", "chrome://extensions")}:
|
|
|
275
275
|
|
|
276
276
|
header("Step 7: Configure OpenCode");
|
|
277
277
|
|
|
278
|
-
// OpenCode config discovery (per upstream docs):
|
|
279
|
-
// - $HOME/.opencode.json
|
|
280
|
-
// - $XDG_CONFIG_HOME/opencode/.opencode.json
|
|
281
|
-
// - ./.opencode.json (project-local)
|
|
282
|
-
// We write the project-local config to avoid touching global state.
|
|
283
|
-
const opencodeJsonPath = join(process.cwd(), ".opencode.json");
|
|
284
|
-
|
|
285
278
|
const desiredPlugin = "@different-ai/opencode-browser";
|
|
286
279
|
|
|
287
280
|
function normalizePlugins(val) {
|
|
@@ -290,51 +283,128 @@ Find it at ${color("cyan", "chrome://extensions")}:
|
|
|
290
283
|
return [];
|
|
291
284
|
}
|
|
292
285
|
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
286
|
+
function stripJsoncComments(contents) {
|
|
287
|
+
return contents
|
|
288
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
289
|
+
.replace(/^\s*\/\/.*$/gm, "");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function sanitizeJson(contents) {
|
|
293
|
+
return stripJsoncComments(contents).replace(/,\s*(\]|\})/g, "$1");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function findOpenCodeConfigPath(configDir) {
|
|
297
|
+
const jsoncPath = join(configDir, "opencode.jsonc");
|
|
298
|
+
if (existsSync(jsoncPath)) return jsoncPath;
|
|
299
|
+
const jsonPath = join(configDir, "opencode.json");
|
|
300
|
+
return jsonPath;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const configOptions = [
|
|
304
|
+
"1) Project (./opencode.json or opencode.jsonc)",
|
|
305
|
+
"2) Global (~/.config/opencode/opencode.json)",
|
|
306
|
+
"3) Custom path",
|
|
307
|
+
"4) Skip (does nothing)",
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
log(`\n${configOptions.join("\n")}`);
|
|
311
|
+
const selection = await ask("Choose config location [1-4]: ");
|
|
312
|
+
|
|
313
|
+
let configPath = null;
|
|
314
|
+
let configDir = null;
|
|
315
|
+
|
|
316
|
+
if (selection === "1") {
|
|
317
|
+
configDir = process.cwd();
|
|
318
|
+
configPath = findOpenCodeConfigPath(configDir);
|
|
319
|
+
} else if (selection === "2") {
|
|
320
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
321
|
+
configDir = xdgConfig ? join(xdgConfig, "opencode") : join(homedir(), ".config", "opencode");
|
|
322
|
+
configPath = findOpenCodeConfigPath(configDir);
|
|
323
|
+
} else if (selection === "3") {
|
|
324
|
+
const customPath = await ask("Enter full path to opencode.json or opencode.jsonc: ");
|
|
325
|
+
if (customPath) {
|
|
326
|
+
configPath = customPath;
|
|
327
|
+
configDir = dirname(customPath);
|
|
328
|
+
} else {
|
|
329
|
+
warn("No path provided. Skipping OpenCode config.");
|
|
303
330
|
}
|
|
331
|
+
} else if (selection === "4") {
|
|
332
|
+
warn("Skipping OpenCode config (does nothing).");
|
|
333
|
+
} else {
|
|
334
|
+
warn("Invalid selection. Skipping OpenCode config.");
|
|
304
335
|
}
|
|
305
336
|
|
|
306
|
-
if (
|
|
307
|
-
const
|
|
337
|
+
if (configPath && configDir) {
|
|
338
|
+
const hasExistingConfig = existsSync(configPath);
|
|
339
|
+
const shouldUpdate = hasExistingConfig
|
|
340
|
+
? await confirm(`Found ${configPath}. Add plugin automatically?`)
|
|
341
|
+
: await confirm(`No config found at ${configPath}. Create one?`);
|
|
342
|
+
|
|
308
343
|
if (shouldUpdate) {
|
|
309
344
|
try {
|
|
310
|
-
|
|
345
|
+
let config = { $schema: "https://opencode.ai/config.json", plugin: [] };
|
|
346
|
+
let canWriteConfig = true;
|
|
347
|
+
|
|
348
|
+
if (hasExistingConfig) {
|
|
349
|
+
const rawConfig = readFileSync(configPath, "utf-8");
|
|
350
|
+
try {
|
|
351
|
+
config = JSON.parse(sanitizeJson(rawConfig));
|
|
352
|
+
} catch (e) {
|
|
353
|
+
error(`Failed to parse ${configPath}: ${e.message}`);
|
|
354
|
+
const shouldOverwrite = await confirm("Config is invalid JSON. Back up and recreate it?");
|
|
355
|
+
if (shouldOverwrite) {
|
|
356
|
+
const backupPath = `${configPath}.bak-${Date.now()}`;
|
|
357
|
+
writeFileSync(backupPath, rawConfig);
|
|
358
|
+
warn(`Backed up invalid config to ${backupPath}`);
|
|
359
|
+
config = { $schema: "https://opencode.ai/config.json", plugin: [] };
|
|
360
|
+
} else {
|
|
361
|
+
canWriteConfig = false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (canWriteConfig) {
|
|
367
|
+
config.plugin = normalizePlugins(config.plugin);
|
|
368
|
+
if (!config.plugin.includes(desiredPlugin)) config.plugin.push(desiredPlugin);
|
|
369
|
+
if (typeof config.$schema !== "string") config.$schema = "https://opencode.ai/config.json";
|
|
370
|
+
|
|
371
|
+
ensureDir(configDir);
|
|
372
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
373
|
+
success(`Updated ${configPath} with plugin`);
|
|
374
|
+
} else {
|
|
375
|
+
warn(`Skipped updating ${configPath}. Fix JSON manually and rerun install.`);
|
|
376
|
+
}
|
|
377
|
+
} catch (e) {
|
|
378
|
+
error(`Failed to update ${configPath}: ${e.message}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
311
382
|
|
|
312
|
-
|
|
313
|
-
config.plugin = normalizePlugins(config.plugin);
|
|
314
|
-
if (!config.plugin.includes(desiredPlugin)) config.plugin.push(desiredPlugin);
|
|
383
|
+
header("Step 8: Optional Agent Skill");
|
|
315
384
|
|
|
316
|
-
|
|
385
|
+
log(`
|
|
386
|
+
Agent Skills are reusable instructions discovered by OpenCode.
|
|
317
387
|
|
|
318
|
-
|
|
319
|
-
|
|
388
|
+
Format rules (summary):
|
|
389
|
+
- Place a skill at .opencode/skill/<name>/SKILL.md
|
|
390
|
+
- SKILL.md must start with YAML frontmatter with name + description
|
|
391
|
+
- name must match the directory and use: ^[a-z0-9]+(-[a-z0-9]+)*$
|
|
392
|
+
`);
|
|
320
393
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
394
|
+
const skillName = "browser-automation";
|
|
395
|
+
const skillSrc = join(PACKAGE_ROOT, ".opencode", "skill", skillName, "SKILL.md");
|
|
396
|
+
const skillDstDir = join(process.cwd(), ".opencode", "skill", skillName);
|
|
397
|
+
const skillDst = join(skillDstDir, "SKILL.md");
|
|
398
|
+
|
|
399
|
+
if (existsSync(skillSrc)) {
|
|
400
|
+
const shouldAddSkill = await confirm(`Add ${skillName} skill to this repo?`);
|
|
401
|
+
if (shouldAddSkill) {
|
|
402
|
+
ensureDir(skillDstDir);
|
|
403
|
+
copyFileSync(skillSrc, skillDst);
|
|
404
|
+
success(`Added skill: ${skillDst}`);
|
|
326
405
|
}
|
|
327
406
|
} else {
|
|
328
|
-
|
|
329
|
-
if (shouldCreate) {
|
|
330
|
-
const config = {
|
|
331
|
-
$schema: "https://opencode.ai/config.json",
|
|
332
|
-
theme: "opencode",
|
|
333
|
-
plugin: [desiredPlugin],
|
|
334
|
-
};
|
|
335
|
-
writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
336
|
-
success("Created .opencode.json with plugin");
|
|
337
|
-
}
|
|
407
|
+
warn("Skill template missing from package; skipping.");
|
|
338
408
|
}
|
|
339
409
|
|
|
340
410
|
header("Installation Complete!");
|
|
@@ -410,7 +480,7 @@ async function uninstall() {
|
|
|
410
480
|
${color("bright", "Note:")}
|
|
411
481
|
- The unpacked extension folder remains at: ${EXTENSION_DIR}
|
|
412
482
|
- Remove it manually in ${color("cyan", "chrome://extensions")}
|
|
413
|
-
- Remove ${color("bright", "@different-ai/opencode-browser")} from your opencode.json plugin list if desired.
|
|
483
|
+
- Remove ${color("bright", "@different-ai/opencode-browser")} from your opencode.json/opencode.jsonc plugin list if desired.
|
|
414
484
|
`);
|
|
415
485
|
}
|
|
416
486
|
|
package/extension/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@different-ai/opencode-browser",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.4",
|
|
4
4
|
"description": "Browser automation plugin for OpenCode (native messaging + per-tab ownership).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"./plugin": "./dist/plugin.js"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
+
".opencode",
|
|
15
16
|
"bin",
|
|
16
17
|
"dist",
|
|
17
18
|
"extension",
|