@opencode/browse 0.0.6-dev.20 → 0.0.6-dev.21

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.
@@ -0,0 +1,154 @@
1
+ import"./main-ek76h9k1.mjs";
2
+ import {
3
+ versionEntry,
4
+ readCurrent,
5
+ npmRuntime
6
+ } from "./main-0m569gdn.mjs";
7
+
8
+ // src/mcp/supervisor.ts
9
+ import { spawn } from "node:child_process";
10
+ import { existsSync } from "node:fs";
11
+ import { createInterface } from "node:readline";
12
+ var directEnvironmentVariable = "OPENCODE_BROWSER_MCP_DIRECT";
13
+ async function runBinaryMcp(args) {
14
+ const runtime = npmRuntime();
15
+ if (!runtime) {
16
+ process.env[directEnvironmentVariable] = "1";
17
+ const { runCli } = await import("./cli-qcdv8aqh.mjs");
18
+ return runCli();
19
+ }
20
+ const { layout } = runtime;
21
+ const REPLAY = "opencode-browser-shim-initialize";
22
+ const relayPort = Number(process.env.OPENCODE_BROWSER_PORT) || 19988;
23
+ const current = () => readCurrent(layout)?.version ?? runtime.version;
24
+ const command = (version) => {
25
+ const entry = versionEntry(layout, version, true);
26
+ return existsSync(entry) ? entry : runtime.current.binary ?? process.execPath;
27
+ };
28
+ process.env.OPENCODE_BROWSER_MCP_SESSION ??= "mcp-" + Math.random().toString(16).slice(2, 10).padEnd(8, "0");
29
+ process.env.OPENCODE_BROWSER_CLIENT_PID = String(process.pid);
30
+ let version = current();
31
+ let child;
32
+ let initialize;
33
+ let initialized;
34
+ let swapping = false;
35
+ let listChanged = false;
36
+ const inflight = new Set;
37
+ const queue = [];
38
+ const toClient = (line) => process.stdout.write(line + `
39
+ `);
40
+ const toChild = (line) => child.stdin.write(line + `
41
+ `);
42
+ const parse = (line) => {
43
+ try {
44
+ return JSON.parse(line);
45
+ } catch {
46
+ return;
47
+ }
48
+ };
49
+ const start = (replay) => {
50
+ const next = spawn(command(version), ["mcp", ...args], { stdio: ["pipe", "pipe", "inherit"], env: { ...process.env, [directEnvironmentVariable]: "1" } });
51
+ next.stdin.on("error", () => {});
52
+ child = next;
53
+ next.on("exit", (code, signal) => {
54
+ if (next !== child)
55
+ return;
56
+ process.exit(code ?? (signal ? 1 : 0));
57
+ });
58
+ createInterface({ input: next.stdout }).on("line", (line) => {
59
+ if (next !== child)
60
+ return;
61
+ const message = parse(line);
62
+ if (message && message.id === REPLAY) {
63
+ if (initialized)
64
+ toChild(initialized);
65
+ swapping = false;
66
+ if (listChanged)
67
+ toClient(JSON.stringify({ jsonrpc: "2.0", method: "notifications/tools/list_changed" }));
68
+ for (const queued of queue.splice(0))
69
+ fromClient(queued);
70
+ return;
71
+ }
72
+ if (message && message.id !== undefined && message.method === undefined) {
73
+ inflight.delete(message.id);
74
+ if (message.result?.capabilities?.tools)
75
+ listChanged = message.result.capabilities.tools.listChanged === true;
76
+ }
77
+ toClient(line);
78
+ maybeSwap();
79
+ });
80
+ if (replay && initialize) {
81
+ swapping = true;
82
+ toChild(JSON.stringify({ ...initialize, id: REPLAY }));
83
+ }
84
+ };
85
+ let heldSince;
86
+ const moved = () => current() !== version;
87
+ const fromClient = (line) => {
88
+ if (swapping)
89
+ return void queue.push(line);
90
+ if (moved()) {
91
+ heldSince ??= Date.now();
92
+ queue.push(line);
93
+ return void maybeSwap();
94
+ }
95
+ const message = parse(line);
96
+ if (message?.method === "initialize")
97
+ initialize = message;
98
+ if (message?.method === "notifications/initialized")
99
+ initialized = line;
100
+ if (message && message.id !== undefined && message.method !== undefined)
101
+ inflight.add(message.id);
102
+ toChild(line);
103
+ };
104
+ let checking = false;
105
+ let unreachable = 0;
106
+ const relayVersion = () => fetch(`http://127.0.0.1:${relayPort}/version`, { signal: AbortSignal.timeout(2000) }).then((response) => response.ok ? response.json() : undefined).then((body) => body && typeof body.version === "string" ? body.version : undefined, () => {
107
+ return;
108
+ });
109
+ const maybeSwap = async () => {
110
+ if (swapping || checking || inflight.size > 0)
111
+ return;
112
+ const next = current();
113
+ if (next === version) {
114
+ unreachable = 0;
115
+ heldSince = undefined;
116
+ for (const queued of queue.splice(0))
117
+ fromClient(queued);
118
+ return;
119
+ }
120
+ checking = true;
121
+ const running = await relayVersion();
122
+ checking = false;
123
+ unreachable = running === undefined ? unreachable + 1 : 0;
124
+ if (running !== next && unreachable < 3) {
125
+ if (heldSince !== undefined && Date.now() - heldSince > 15000) {
126
+ heldSince = undefined;
127
+ for (const line of queue.splice(0)) {
128
+ const message = parse(line);
129
+ if (message && message.id !== undefined && message.method !== undefined)
130
+ inflight.add(message.id);
131
+ toChild(line);
132
+ }
133
+ }
134
+ return;
135
+ }
136
+ if (swapping || inflight.size > 0 || current() !== next)
137
+ return;
138
+ const old = child;
139
+ version = next;
140
+ heldSince = undefined;
141
+ start(true);
142
+ old.stdin.end();
143
+ setTimeout(() => old.kill(), 5000).unref();
144
+ };
145
+ start(false);
146
+ createInterface({ input: process.stdin }).on("line", fromClient).on("close", () => {
147
+ child.stdin.end();
148
+ setTimeout(() => process.exit(0), 5000).unref();
149
+ });
150
+ setInterval(() => void maybeSwap(), 1000).unref();
151
+ }
152
+ export {
153
+ runBinaryMcp
154
+ };
@@ -4,8 +4,10 @@ import {
4
4
  updateNotice2,
5
5
  createUpdateWatcher2,
6
6
  runUpdateCommand2
7
- } from "./main-3rsfghef.mjs";
8
- import"./main-ma25hhqh.mjs";
7
+ } from "./main-33kf3cfr.mjs";
8
+ import"./main-0m569gdn.mjs";
9
+ import"./main-ek76h9k1.mjs";
10
+ import"./main-3gthmkzw.mjs";
9
11
  export {
10
12
  createUpdateWatcher2 as createUpdateWatcher,
11
13
  formatUpdateNotice2 as formatUpdateNotice,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  opencodeBrowserVersion2,
3
3
  opencodeBrowserBuildId2
4
- } from "./main-arx3pfyn.mjs";
4
+ } from "./main-ek76h9k1.mjs";
5
5
  export {
6
6
  opencodeBrowserBuildId2 as opencodeBrowserBuildId,
7
7
  opencodeBrowserVersion2 as opencodeBrowserVersion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@opencode/browse",
3
- "version": "0.0.6-dev.20",
4
- "description": "Browse: connect the extension to opencode, and drive your browser from any agent (CLI, MCP, Playwright).",
3
+ "version": "0.0.6-dev.21",
4
+ "description": "browse: connect the extension to opencode, and drive your browser from any agent (CLI, MCP, Playwright).",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@clack/core": "1.5.1",
41
+ "@clack/prompts": "1.8.1",
41
42
  "@types/bun": "1.4.0",
42
43
  "@types/chrome": "0.3.4",
43
44
  "@types/node": "26.6.4",
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: browse
3
- description: Use the user's existing Chromium-family browser through Browse, with deterministic Playwright. Use when asked to inspect, automate, test, or interact with a visible browser tab; continue an authenticated browser workflow; handle 2FA, passkeys, CAPTCHAs, or payment confirmation; record browser behavior; or capture an authenticated network flow.
3
+ description: Use the user's existing Chromium-family browser through browse, with deterministic Playwright. Use when asked to inspect, automate, test, or interact with a visible browser tab; continue an authenticated browser workflow; handle 2FA, passkeys, CAPTCHAs, or payment confirmation; record browser behavior; or capture an authenticated network flow.
4
4
  ---
5
5
 
6
- # Browse
6
+ # browse
7
7
 
8
8
  The `browse` tools (MCP server `browse`; the same commands as the `opencode-browser`
9
- CLI) drive the user's browser through the Browse extension.
10
- Browse is a **driver**, not an agent. The calling agent decides what to
11
- do; Browse runs deterministic Playwright code in the user's visible
9
+ CLI) drive the user's browser through the browse extension.
10
+ browse is a **driver**, not an agent. The calling agent decides what to
11
+ do; browse runs deterministic Playwright code in the user's visible
12
12
  browser.
13
13
 
14
14
  Use one loop throughout: **inspect, act, verify**. Inspect the real page before
@@ -55,7 +55,7 @@ Completion: one execute returns a page result and a readable session id, or
55
55
  ### 2. Choose The Page Deliberately
56
56
 
57
57
  DevTools-attached tabs
58
- are excluded from Browse's inventory. `page.title()` reads time out
58
+ are excluded from browse's inventory. `page.title()` reads time out
59
59
  after five seconds if the page execution context remains unavailable; the read
60
60
  timeout does not close or replace the tab.
61
61
 
@@ -119,7 +119,7 @@ exact Playwright accessible name.
119
119
  `targetUrl` and `targetIndex` select existing attached pages; they never
120
120
  navigate. A URL selector must match exactly one page, and URL and index selectors
121
121
  cannot be combined. Adoption makes that tab the session default, closes the
122
- session's previous relay-created page, and is exclusive to one Browse
122
+ session's previous relay-created page, and is exclusive to one browse
123
123
  session. Reset or delete releases an adopted user tab without closing it.
124
124
 
125
125
  Adoption binds ownership and exact target identity before initializing automation.
@@ -187,7 +187,7 @@ not merely evidence that an action was attempted.
187
187
  ### 4. Continue Or Finish Cleanly
188
188
 
189
189
  Named sessions preserve their default page across short-lived CLI and MCP
190
- processes. They also survive relay restarts: Browse restores the id,
190
+ processes. They also survive relay restarts: browse restores the id,
191
191
  read-only mode, and exact default target. JavaScript `state` and snapshot refs
192
192
  are process-local and reset after a relay restart with an explicit warning.
193
193
 
@@ -228,7 +228,7 @@ work.
228
228
 
229
229
  ## Canonical Authenticated Flow
230
230
 
231
- The distinguishing Browse workflow is an authenticated tab plus a
231
+ The distinguishing browse workflow is an authenticated tab plus a
232
232
  human-only prompt:
233
233
 
234
234
  Attach and adopt the existing tab, inspect its real UI, fill ordinary fields,
@@ -237,7 +237,7 @@ After the user completes it, verify the authenticated destination. The same
237
237
  session can continue after an MCP process or relay restart.
238
238
 
239
239
  When the prompt-triggering action may itself block, put only that action in
240
- `start`. Browse presents and acknowledges WAIT before invoking it:
240
+ `start`. browse presents and acknowledges WAIT before invoking it:
241
241
 
242
242
  ```js
243
243
  await handoff("Complete the security-key prompt, then continue", {
@@ -252,7 +252,7 @@ await page.getByRole("heading", { name: /account|dashboard/i }).waitFor()
252
252
  return { authenticatedUrl: page.url(), title: await page.title() }
253
253
  ```
254
254
 
255
- After a resolved handoff, Browse waits through transient destination
255
+ After a resolved handoff, browse waits through transient destination
256
256
  context replacement before returning, so this verification can remain in the
257
257
  same execute.
258
258
 
@@ -287,7 +287,7 @@ code; a demonstration records actions, not proof that the workflow succeeded.
287
287
 
288
288
  ### Bot Challenges And Blocks
289
289
 
290
- After an execute call navigates or fails, Browse looks at the session
290
+ After an execute call navigates or fails, browse looks at the session
291
291
  page for a bot challenge or block page (Cloudflare, Turnstile, Akamai, DataDome,
292
292
  PerimeterX/HUMAN, Imperva, AWS WAF, reCAPTCHA or hCaptcha walls, generic
293
293
  "access denied" and "verify you are human" pages) and reports it as
@@ -399,7 +399,7 @@ diffs include visible page content: inspect for private information before shari
399
399
  Execute code can use `page`, `context`, `browser`, persistent `state`, the
400
400
  `files` helper, compute-only Node modules through `modules` and the aliases
401
401
  `path`, `url`, `util`, `crypto`, `buffer`, `zlib`, `events` and `stream`, plus
402
- the Browse helpers documented here. Timers, `URL`, `TextEncoder`,
402
+ the browse helpers documented here. Timers, `URL`, `TextEncoder`,
403
403
  `Buffer`, `structuredClone` and `crypto.randomUUID()` are available. Use
404
404
  `page.evaluate` for `window`, `document`, storage, and same-origin `fetch`
405
405
  with page cookies. `page.evaluate` runs in an isolated world, whose `fetch`
@@ -503,12 +503,12 @@ return { tools, result }
503
503
  ```
504
504
 
505
505
  Discovery covers all frames. If the same name appears in multiple frames, pass
506
- the exact reported frame label as `{ frame }`. Browse re-discovers the
506
+ the exact reported frame label as `{ frame }`. browse re-discovers the
507
507
  tool immediately before invoking it, so stale registrations fail directly.
508
508
 
509
509
  ## Safety
510
510
 
511
- Browse blocks CDP commands that would destroy shared browser state,
511
+ browse blocks CDP commands that would destroy shared browser state,
512
512
  including browser close and cookie/cache clearing. Never work around those
513
513
  guardrails. Cookies and site storage (IndexedDB, Cache Storage, local storage,
514
514
  quota) are limited to the hosts of your session's pages, and navigations to
@@ -546,7 +546,7 @@ match; hostnames are resolved, and the document's real remote IP is checked, so
546
546
  a name pointing at a private address is caught too.
547
547
 
548
548
  With an allowlist, `page.goto()` and `context.newPage()` to another host fail
549
- with "Browse blocked Page.navigate: session ... has a domain
549
+ with "browse blocked Page.navigate: session ... has a domain
550
550
  allowlist". Links, redirects, scripts and form posts that leave the list are
551
551
  stopped, or the tab is sent back to `about:blank`; popups that open elsewhere
552
552
  are closed. Each one adds a "Domain allowlist:" warning to the execute result.
@@ -568,7 +568,7 @@ await secrets.type(page.getByLabel("Password"), "example") // or a selector st
568
568
  await page.getByRole("button", { name: "Sign in" }).click()
569
569
  ```
570
570
 
571
- Browse checks the field's frame URL (from the browser's frame tree,
571
+ browse checks the field's frame URL (from the browser's frame tree,
572
572
  not page script) against the secret's allowed origins, and the session's
573
573
  allowlist when it has one, right before it inserts the value. It returns
574
574
  `{ typed: true, name, origin, kind }`, never the value, and redacts the value
@@ -582,7 +582,7 @@ elsewhere.
582
582
 
583
583
  ### Signing in with Deadbolt
584
584
 
585
- When the user connected Browse to Deadbolt (`opencode-browser deadbolt
585
+ When the user connected browse to Deadbolt (`opencode-browser deadbolt
586
586
  connect`), you can sign in with the logins they granted to agents, without
587
587
  seeing any value. `secrets_list` shows them for the session's allowed sites:
588
588
  title, username, `granted`, which `fields` (username, password, totp) and
@@ -808,7 +808,7 @@ values fail instead of silently clamping. The start result reports the chosen ra
808
808
  Tab capture can include audio; CDP requires `ffmpeg` and has no audio. Use the
809
809
  command's `--help` for format options.
810
810
 
811
- Recordings are clean by default: nothing from Browse is drawn in the
811
+ Recordings are clean by default: nothing from browse is drawn in the
812
812
  recorded page. The ghost cursor stays off, `screenshotWithLabels` draws its
813
813
  labels onto the returned image instead of the page, and a handoff on a tab being
814
814
  recorded shows no in-page card: the user gets a system notification and the
@@ -918,12 +918,12 @@ Common diagnoses:
918
918
  `page.url()` read can still work; retry the page read after the page settles.
919
919
  Ordinary missing-locator timeouts do not receive this diagnostic.
920
920
  - `connected:false`: check `extension.pairingState` and follow `extension.nextStep`.
921
- `unpaired` or `refused` means Browse is installed: re-run install to pair it, **not** the store.
922
- `paired-offline`: reopen the paired browser profile and enable Browse; its worker wakes every 30 seconds.
923
- `not-seen`: the relay hasn't heard from a browser. If Browse is already added, open that browser and
921
+ `unpaired` or `refused` means browse is installed: re-run install to pair it, **not** the store.
922
+ `paired-offline`: reopen the paired browser profile and enable browse; its worker wakes every 30 seconds.
923
+ `not-seen`: the relay hasn't heard from a browser. If browse is already added, open that browser and
924
924
  rerun install. Otherwise follow install's store or Load unpacked steps.
925
925
  - Incompatible extension protocol: follow `extension.nextStep`. For an older unpacked extension after setup,
926
- ask the user: “Reload Browse on your browser's extensions page (↻).” Running setup again does not reload older workers.
926
+ ask the user: “Reload browse on your browser's extensions page (↻).” Running setup again does not reload older workers.
927
927
  - Competing browser/profile connections: the active browser is preserved and
928
928
  additional connections are rejected. Use one browser/profile per relay;
929
929
  repeatedly creating sessions or resetting tabs does not switch browsers.
@@ -946,7 +946,7 @@ Common diagnoses:
946
946
  - Extension installed but not paired: re-run `npx @opencode/browse install` to open its pairing link
947
947
  (from a checkout, `bun run setup`). To pair manually, run `npx @opencode/browse pair`
948
948
  (from the checkout root, `node cli/dist/cli.mjs pair`). This starts the relay. Have the user enter
949
- the code in Browse's Options and click Connect. The terminal confirms which browser paired.
949
+ the code in browse's Options and click Connect. The terminal confirms which browser paired.
950
950
  - HTTP 401 from the relay: clients need the relay token from the data root
951
951
  (`relay-token`); the CLI and MCP server send it, other CDP clients must add
952
952
  `Authorization: Bearer <token>` or `?opencodeBrowserToken=<token>`.
@@ -954,10 +954,10 @@ Common diagnoses:
954
954
  and snapshot refs reset. Continue after the warning.
955
955
  - Reset/delete after an extension update may wait briefly for target
956
956
  re-announcement; if the old relay-owned target is absent from the completed
957
- inventory, Browse forgets the dead identity without closing a
957
+ inventory, browse forgets the dead identity without closing a
958
958
  guessed tab.
959
- - Repeated execution-context errors: run one short follow-up so Browse
960
- can health-check the page. A live page is kept: Browse reconnects and
959
+ - Repeated execution-context errors: run one short follow-up so browse
960
+ can health-check the page. A live page is kept: browse reconnects and
961
961
  re-resolves the same tab once, then fails with a `session-page/*-unresponsive`
962
962
  diagnosis if the page still does not answer. Blank or unknown URLs are preserved:
963
963
  they can contain unsaved content. Only a crashed or
@@ -967,7 +967,7 @@ Common diagnoses:
967
967
  stall the main world for automation while rendering normally for the human),
968
968
  open a fresh tab with `context.newPage()` or hand the tab to the user.
969
969
  - Handoff ends with "page execution context did not become available": the user
970
- finished; only Browse's view of the tab is stale. Run a short
970
+ finished; only browse's view of the tab is stale. Run a short
971
971
  follow-up execute so the page is re-checked instead of assuming it was lost.
972
972
  - Fill timeout on login fields: inspect first, then try `fillInput` after
973
973
  confirming the selector or locator resolves. String selectors search open
@@ -992,7 +992,7 @@ For deeper relay diagnosis, restart with `OPENCODE_BROWSER_DEBUG=1`. Debug trace
992
992
  must never include expressions, arguments, results, headers, cookies, or form
993
993
  values.
994
994
 
995
- Whenever Browse fails, wedges, replaces a page/session, or behaves
995
+ Whenever browse fails, wedges, replaces a page/session, or behaves
996
996
  unexpectedly, create or update a `opencode-browser` project todo with the Browser
997
997
  Control version, safe session/page context, exact error, deterministic
998
998
  reproduction, expected versus actual behavior, and recovery attempted. Never
@@ -1,4 +0,0 @@
1
- // src/version.ts
2
- var opencodeBrowserVersion2 = "0.0.6-dev.20";
3
- var opencodeBrowserBuildId2 = "2026-11-04T16:45:59.381Z";
4
- export { opencodeBrowserVersion2, opencodeBrowserBuildId2 };