@different-ai/opencode-browser 4.2.1 → 4.2.3
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 +84 -39
- 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,103 @@ 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 findOpenCodeConfigPath(configDir) {
|
|
293
|
+
const jsoncPath = join(configDir, "opencode.jsonc");
|
|
294
|
+
if (existsSync(jsoncPath)) return jsoncPath;
|
|
295
|
+
const jsonPath = join(configDir, "opencode.json");
|
|
296
|
+
return jsonPath;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const configOptions = [
|
|
300
|
+
"1) Project (./opencode.json or opencode.jsonc)",
|
|
301
|
+
"2) Global (~/.config/opencode/opencode.json)",
|
|
302
|
+
"3) Custom path",
|
|
303
|
+
"4) Skip (does nothing)",
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
log(`\n${configOptions.join("\n")}`);
|
|
307
|
+
const selection = await ask("Choose config location [1-4]: ");
|
|
308
|
+
|
|
309
|
+
let configPath = null;
|
|
310
|
+
let configDir = null;
|
|
311
|
+
|
|
312
|
+
if (selection === "1") {
|
|
313
|
+
configDir = process.cwd();
|
|
314
|
+
configPath = findOpenCodeConfigPath(configDir);
|
|
315
|
+
} else if (selection === "2") {
|
|
316
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
317
|
+
configDir = xdgConfig ? join(xdgConfig, "opencode") : join(homedir(), ".config", "opencode");
|
|
318
|
+
configPath = findOpenCodeConfigPath(configDir);
|
|
319
|
+
} else if (selection === "3") {
|
|
320
|
+
const customPath = await ask("Enter full path to opencode.json or opencode.jsonc: ");
|
|
321
|
+
if (customPath) {
|
|
322
|
+
configPath = customPath;
|
|
323
|
+
configDir = dirname(customPath);
|
|
324
|
+
} else {
|
|
325
|
+
warn("No path provided. Skipping OpenCode config.");
|
|
303
326
|
}
|
|
327
|
+
} else if (selection === "4") {
|
|
328
|
+
warn("Skipping OpenCode config (does nothing).");
|
|
329
|
+
} else {
|
|
330
|
+
warn("Invalid selection. Skipping OpenCode config.");
|
|
304
331
|
}
|
|
305
332
|
|
|
306
|
-
if (
|
|
307
|
-
const
|
|
333
|
+
if (configPath && configDir) {
|
|
334
|
+
const hasExistingConfig = existsSync(configPath);
|
|
335
|
+
const shouldUpdate = hasExistingConfig
|
|
336
|
+
? await confirm(`Found ${configPath}. Add plugin automatically?`)
|
|
337
|
+
: await confirm(`No config found at ${configPath}. Create one?`);
|
|
338
|
+
|
|
308
339
|
if (shouldUpdate) {
|
|
309
340
|
try {
|
|
310
|
-
const config =
|
|
341
|
+
const config = hasExistingConfig
|
|
342
|
+
? JSON.parse(stripJsoncComments(readFileSync(configPath, "utf-8")))
|
|
343
|
+
: { $schema: "https://opencode.ai/config.json", plugin: [] };
|
|
311
344
|
|
|
312
|
-
// Make sure plugin is an array.
|
|
313
345
|
config.plugin = normalizePlugins(config.plugin);
|
|
314
346
|
if (!config.plugin.includes(desiredPlugin)) config.plugin.push(desiredPlugin);
|
|
315
|
-
|
|
316
|
-
removeLegacyMcp(config);
|
|
317
|
-
|
|
318
|
-
// Ensure schema is correct if present.
|
|
319
347
|
if (typeof config.$schema !== "string") config.$schema = "https://opencode.ai/config.json";
|
|
320
348
|
|
|
321
|
-
|
|
322
|
-
|
|
349
|
+
ensureDir(configDir);
|
|
350
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
351
|
+
success(`Updated ${configPath} with plugin`);
|
|
323
352
|
} catch (e) {
|
|
324
|
-
error(`Failed to update
|
|
353
|
+
error(`Failed to update ${configPath}: ${e.message}`);
|
|
325
354
|
}
|
|
326
355
|
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
header("Step 8: Optional Agent Skill");
|
|
359
|
+
|
|
360
|
+
log(`
|
|
361
|
+
Agent Skills are reusable instructions discovered by OpenCode.
|
|
362
|
+
|
|
363
|
+
Format rules (summary):
|
|
364
|
+
- Place a skill at .opencode/skill/<name>/SKILL.md
|
|
365
|
+
- SKILL.md must start with YAML frontmatter with name + description
|
|
366
|
+
- name must match the directory and use: ^[a-z0-9]+(-[a-z0-9]+)*$
|
|
367
|
+
`);
|
|
368
|
+
|
|
369
|
+
const skillName = "browser-automation";
|
|
370
|
+
const skillSrc = join(PACKAGE_ROOT, ".opencode", "skill", skillName, "SKILL.md");
|
|
371
|
+
const skillDstDir = join(process.cwd(), ".opencode", "skill", skillName);
|
|
372
|
+
const skillDst = join(skillDstDir, "SKILL.md");
|
|
373
|
+
|
|
374
|
+
if (existsSync(skillSrc)) {
|
|
375
|
+
const shouldAddSkill = await confirm(`Add ${skillName} skill to this repo?`);
|
|
376
|
+
if (shouldAddSkill) {
|
|
377
|
+
ensureDir(skillDstDir);
|
|
378
|
+
copyFileSync(skillSrc, skillDst);
|
|
379
|
+
success(`Added skill: ${skillDst}`);
|
|
337
380
|
}
|
|
381
|
+
} else {
|
|
382
|
+
warn("Skill template missing from package; skipping.");
|
|
338
383
|
}
|
|
339
384
|
|
|
340
385
|
header("Installation Complete!");
|
|
@@ -410,7 +455,7 @@ async function uninstall() {
|
|
|
410
455
|
${color("bright", "Note:")}
|
|
411
456
|
- The unpacked extension folder remains at: ${EXTENSION_DIR}
|
|
412
457
|
- Remove it manually in ${color("cyan", "chrome://extensions")}
|
|
413
|
-
- Remove ${color("bright", "@different-ai/opencode-browser")} from your opencode.json plugin list if desired.
|
|
458
|
+
- Remove ${color("bright", "@different-ai/opencode-browser")} from your opencode.json/opencode.jsonc plugin list if desired.
|
|
414
459
|
`);
|
|
415
460
|
}
|
|
416
461
|
|
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.3",
|
|
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",
|