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

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,112 @@
1
+ // src/protocol.ts
2
+ var extensionProtocolVersion = 3;
3
+ var minExtensionProtocolVersion = 3;
4
+ var unpackedExtensionId = "afeafocngkodbmaipcngoamamfmekgfo";
5
+ var unpackedReloadAdvice = "Reload browse on your browser's extensions page (↻).";
6
+ var checkoutReloadPrefix = "Extension protocol incompatible: extension-older; reload ";
7
+ var extensionReconnectAlarmPeriodMs = 30000;
8
+ var legacyExtensionProtocolVersion = 1;
9
+ function extensionProtocolCompatibility(value) {
10
+ const legacy = value === undefined;
11
+ const valid = typeof value === "number" && Number.isSafeInteger(value) && value > 0;
12
+ const version = valid ? value : legacy ? legacyExtensionProtocolVersion : null;
13
+ return { version, compatible: version !== null && version >= minExtensionProtocolVersion && version <= extensionProtocolVersion, legacy };
14
+ }
15
+ function extensionProtocolSkew(version) {
16
+ if (typeof version !== "number")
17
+ return;
18
+ if (version > extensionProtocolVersion)
19
+ return "extension-newer";
20
+ if (version < minExtensionProtocolVersion)
21
+ return "extension-older";
22
+ return;
23
+ }
24
+ function extensionProtocolAdvice(version, commands, distribution) {
25
+ const skew = extensionProtocolSkew(version);
26
+ const versions = `extension protocol ${version ?? "unknown"}, this relay speaks ${minExtensionProtocolVersion === extensionProtocolVersion ? extensionProtocolVersion : `${minExtensionProtocolVersion} to ${extensionProtocolVersion}`}`;
27
+ if (skew === "extension-newer")
28
+ return `The browse extension is newer than this browse CLI (${versions}). Update the CLI: run \`${commands.install}\` (an npm install with automatic updates also catches up by itself within a day), then restart opencode.`;
29
+ if (skew === "extension-older") {
30
+ const store = "The Chrome Web Store updates it within a few hours (to update now, open chrome://extensions, turn on Developer mode and click Update).";
31
+ const next = distribution === "unpacked" ? unpackedReloadAdvice : distribution === "store" ? store : `For an unpacked extension: ${unpackedReloadAdvice} For a store extension: ${store}`;
32
+ return `The browse extension is older than this browse CLI (${versions}). ${next}`;
33
+ }
34
+ return `The browse extension and this browse CLI don't speak the same protocol (${versions}). Run \`${commands.install}\` again and update the extension.`;
35
+ }
36
+ function makePageStatus(options) {
37
+ return {
38
+ state: options.state,
39
+ owner: options.targetOwner === "user" ? "user" : "session",
40
+ ...options.sessionId ? { sessionId: options.sessionId } : {},
41
+ ...options.sessionId && options.readOnly ? { readOnly: true } : {},
42
+ ...options.message ? { message: options.message } : {},
43
+ ...options.handoffId ? { handoffId: options.handoffId } : {},
44
+ ...options.recording ? { recording: true } : {}
45
+ };
46
+ }
47
+ var extensionCommandMethodValues = [
48
+ "ping",
49
+ "debugger.attach",
50
+ "debugger.detach",
51
+ "debugger.sendCommand",
52
+ "tabs.create",
53
+ "tabs.remove",
54
+ "tabs.group",
55
+ "tabs.ungroup",
56
+ "action.setAttached",
57
+ "action.setBadge",
58
+ "pageStatus.set",
59
+ "pageStatus.clear",
60
+ "runtime.reload",
61
+ "recording.start",
62
+ "recording.stop",
63
+ "recording.status",
64
+ "recording.cancel",
65
+ "tabs.cleanup",
66
+ "update.notice",
67
+ "relay.settings",
68
+ "settings.get",
69
+ "settings.set"
70
+ ];
71
+ var extensionEventMethodValues = [
72
+ "protocol",
73
+ "hello",
74
+ "auth.code",
75
+ "auth.response",
76
+ "ready",
77
+ "toolbar.clicked",
78
+ "handoff.completed",
79
+ "debugger.event",
80
+ "debugger.attached",
81
+ "debugger.detached",
82
+ "tabs.removed",
83
+ "tabs.opened",
84
+ "pong",
85
+ "log",
86
+ "recording.cancelled",
87
+ "pageStatus.requested",
88
+ "settings"
89
+ ];
90
+ function parseJsonObject(input) {
91
+ const parsed = JSON.parse(input);
92
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
93
+ throw new Error("Expected JSON object");
94
+ }
95
+ return parsed;
96
+ }
97
+ var extensionCommandMethods = new Set(extensionCommandMethodValues);
98
+ var extensionEventMethods = new Set(extensionEventMethodValues);
99
+ function isCdpRequest(input) {
100
+ return typeof input.id === "number" && typeof input.method === "string" && (input.params === undefined || isJsonObject(input.params)) && (input.sessionId === undefined || typeof input.sessionId === "string");
101
+ }
102
+ function isExtensionResponse(input) {
103
+ return typeof input.id === "number" && (input.result === undefined || isJsonObject(input.result)) && (input.error === undefined || typeof input.error === "string") && !(input.result !== undefined && input.error !== undefined);
104
+ }
105
+ function isExtensionEvent(input) {
106
+ return typeof input.method === "string" && extensionEventMethods.has(input.method) && (input.params === undefined || isJsonObject(input.params));
107
+ }
108
+ function isJsonObject(value) {
109
+ return value !== null && typeof value === "object" && !Array.isArray(value);
110
+ }
111
+
112
+ export { extensionProtocolVersion, minExtensionProtocolVersion, unpackedExtensionId, unpackedReloadAdvice, checkoutReloadPrefix, extensionReconnectAlarmPeriodMs, extensionProtocolCompatibility, extensionProtocolSkew, extensionProtocolAdvice, makePageStatus, parseJsonObject, isCdpRequest, isExtensionResponse, isExtensionEvent, isJsonObject };
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  writePrivateJsonFileSync
3
- } from "./main-20ahaz2q.mjs";
3
+ } from "./main-z53824j2.mjs";
4
4
  import {
5
5
  dataRoot,
6
6
  devExtensionUrl,
7
7
  devExtensionFolder
8
- } from "./main-ma25hhqh.mjs";
8
+ } from "./main-drvnesx3.mjs";
9
9
 
10
10
  // src/relay/browser-pairing.ts
11
11
  import crypto from "node:crypto";
@@ -195,7 +195,7 @@ async function syncDevExtension(extension, options = {}) {
195
195
  }
196
196
  const manifest = JSON.parse(readFileSync(path2.join(staging, "manifest.json"), "utf8"));
197
197
  if (!manifest.key)
198
- throw new Error("the dev extension has no manifest key, so it wouldn't have Browse's unpacked ID");
198
+ throw new Error("the dev extension has no manifest key, so it wouldn't have browse's unpacked ID");
199
199
  writeExtensionPairing(staging, mintPairingCode(root, { ttlMs: bundledPairingCodeTtlMs }));
200
200
  const build = (() => {
201
201
  try {
@@ -300,4 +300,4 @@ function isLocalPeer(socket) {
300
300
  return remote !== undefined && (remote === "::1" || remote.startsWith("127."));
301
301
  }
302
302
 
303
- export { context, pairingCodeTtlMs, bundledPairingCodeTtlMs, formatPairingCode, pairingCodeKey, mintPairingCode, pendingPairingCodeKeys, consumePairingCode, recordPairingFailure, writeExtensionPairing, readBrowserPairings, browserToken, newBrowserToken, issueBrowserToken, touchBrowserPairing, revokeBrowserToken, legacyPairingCode, removeLegacyPairingCode, legacyPairingProof, pairingProof, ecdhExchange, pairingSessionKey, sessionProof, wrapToken, installedDevExtension, syncDevExtension, parseHostname, readRelayConfig, writeRelayConfig, relayHostname, isLocalPeer };
303
+ export { parseHostname, readRelayConfig, writeRelayConfig, relayHostname, isLocalPeer, context, pairingCodeTtlMs, bundledPairingCodeTtlMs, formatPairingCode, pairingCodeKey, mintPairingCode, pendingPairingCodeKeys, consumePairingCode, recordPairingFailure, writeExtensionPairing, readBrowserPairings, browserToken, newBrowserToken, issueBrowserToken, touchBrowserPairing, revokeBrowserToken, legacyPairingCode, removeLegacyPairingCode, legacyPairingProof, pairingProof, ecdhExchange, pairingSessionKey, sessionProof, wrapToken, installedDevExtension, syncDevExtension };
@@ -1,9 +1,15 @@
1
+ import {
2
+ opencodeBrowserVersion2
3
+ } from "./main-q8c2ta3y.mjs";
4
+
1
5
  // src/update/auto-update.ts
2
6
  import { spawn } from "node:child_process";
3
- import { chmodSync, closeSync, cpSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
7
+ import { createHash } from "node:crypto";
8
+ import { chmodSync, closeSync, copyFileSync, cpSync, existsSync, linkSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
9
  import { createRequire } from "node:module";
5
10
  import path2 from "node:path";
6
11
  import { fileURLToPath as fileURLToPath2 } from "node:url";
12
+ import { gunzipSync } from "node:zlib";
7
13
 
8
14
  // src/paths.ts
9
15
  import fs from "node:fs";
@@ -22,13 +28,33 @@ function dataRoot(home = os.homedir()) {
22
28
  var packageRoot = () => path.dirname(path.dirname(fileURLToPath(import.meta.url)));
23
29
  var oldStoreMarker = () => path.join(dataRoot(), "old-store-extension");
24
30
  function fromCheckout() {
25
- return fs.existsSync(path.join(packageRoot(), "src", "main.ts"));
31
+ return !compiledBinary() && fs.existsSync(path.join(packageRoot(), "src", "main.ts"));
32
+ }
33
+ var compiledBinary = () => globalThis.__OPENCODE_BROWSER_COMPILED__ === true;
34
+ var binaryPackage = () => globalThis.__OPENCODE_BROWSER_TARGET__ ? `@opencode/browse-${globalThis.__OPENCODE_BROWSER_TARGET__}` : undefined;
35
+ var binaryName = (platform = process.platform) => platform === "win32" ? "browse.exe" : "browse";
36
+ var scriptEntry = (entry) => /\.[cm]?[jt]s$/.test(entry);
37
+ var entryCommand = (entry, node = process.execPath) => scriptEntry(entry) ? [node, entry] : [entry];
38
+ function installedBinary() {
39
+ try {
40
+ const binary = JSON.parse(fs.readFileSync(path.join(dataRoot(), "runtime", "current.json"), "utf8")).binary;
41
+ return typeof binary === "string" ? binary : undefined;
42
+ } catch {
43
+ return;
44
+ }
26
45
  }
46
+ var runningEntry = () => compiledBinary() ? process.execPath : process.argv[1];
27
47
  function checkoutRoot() {
28
48
  return fromCheckout() ? path.dirname(packageRoot()) : undefined;
29
49
  }
30
50
  var quoted = (value) => /^[\w@%+=:,./-]+$/.test(value) ? value : JSON.stringify(value);
31
51
  function cliCommand(args) {
52
+ if (compiledBinary()) {
53
+ const binary = installedBinary() ?? process.execPath;
54
+ const dir = path.dirname(binary);
55
+ const onPath = (process.env.PATH ?? "").split(path.delimiter).some((entry) => entry && path.resolve(entry) === dir);
56
+ return `${onPath ? "browse" : quoted(binary)} ${args}`;
57
+ }
32
58
  if (fromCheckout())
33
59
  return `node ${quoted(path.join(packageRoot(), "dist", "cli.mjs"))} ${args}`;
34
60
  const shim = path.join(dataRoot(), "bin", "opencode-browser.mjs");
@@ -43,13 +69,15 @@ function storeConfig(env = process.env, baked = { id: "mfnicocicmmlkpjnaffgihfjh
43
69
  return id ? { id } : {};
44
70
  }
45
71
  var RELEASE_SERVICE = "https://anoma.ly/release";
46
- function devExtension(baked = { file: "browse-extension-0.0.6-dev.20.zip", sha256: "05ac10ddd60291753c1e7fd38a440660b2486d7e3f6117ad2de872f1931d521c", size: 39161 }) {
72
+ function devExtension(baked = { file: "browse-extension-0.0.6-dev.22.zip", sha256: "5668ffae58f159a2273986282e4cba34543dacee5c31a87131c18d6a60a2021a", size: 172632 }) {
47
73
  return baked && /^[0-9a-f]{64}$/.test(baked.sha256) && /^[\w.-]+\.zip$/.test(baked.file) ? baked : undefined;
48
74
  }
49
75
  var devExtensionUrl = (extension, env = process.env) => `${(env.OPENCODE_BROWSER_RELEASE_SERVICE || RELEASE_SERVICE).replace(/\/+$/, "")}/browse/files/${extension.sha256}/${extension.file}`;
50
76
  var devExtensionFolder = (root = dataRoot()) => path.join(root, "extension");
51
77
  var storeUrl = (id) => `https://chromewebstore.google.com/detail/${id}`;
52
78
  function installCommand() {
79
+ if (compiledBinary())
80
+ return cliCommand("install");
53
81
  return fromCheckout() ? `cd ${quoted(path.dirname(packageRoot()))} && bun run setup` : "npx @opencode/browse install";
54
82
  }
55
83
 
@@ -70,7 +98,8 @@ function runtimeLayout(root = dataRoot()) {
70
98
  };
71
99
  }
72
100
  var versionDirectory = (layout, version) => path2.join(layout.versions, version);
73
- var versionEntry = (layout, version) => path2.join(versionDirectory(layout, version), "node_modules", "@opencode", "browse", "dist", "cli.mjs");
101
+ var versionEntry = (layout, version, binary = compiledBinary()) => binary ? path2.join(versionDirectory(layout, version), binaryName()) : path2.join(versionDirectory(layout, version), "node_modules", "@opencode", "browse", "dist", "cli.mjs");
102
+ var updatePackage = () => (compiledBinary() ? binaryPackage() : undefined) ?? PACKAGE_NAME;
74
103
  var channelOf = (version) => version.includes("-dev.") ? "dev" : "latest";
75
104
  var UPDATE_VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-dev\.(\d+)(?:\.(\d+))?)?$/;
76
105
  function compareVersions(left, right) {
@@ -130,6 +159,8 @@ function npmRuntime(options = {}) {
130
159
  if (options.checkout ?? fromCheckout())
131
160
  return;
132
161
  const layout = options.layout ?? runtimeLayout();
162
+ if (compiledBinary())
163
+ return binaryRuntime(layout);
133
164
  const defaultEntry = () => fileURLToPath2(import.meta.url.endsWith(".ts") ? import.meta.url : new URL("./cli.mjs", import.meta.url));
134
165
  const entry = realpath(options.entry ?? defaultEntry());
135
166
  const relative = path2.relative(realpath(layout.versions), entry).split(path2.sep);
@@ -140,6 +171,16 @@ function npmRuntime(options = {}) {
140
171
  return;
141
172
  return { layout, version: relative[0], current };
142
173
  }
174
+ function binaryRuntime(layout) {
175
+ const current = readCurrent(layout);
176
+ if (!current?.binary)
177
+ return;
178
+ const self = realpath(process.execPath);
179
+ const inVersions = !path2.relative(realpath(layout.versions), self).startsWith("..");
180
+ if (self !== realpath(current.binary) && !inVersions)
181
+ return;
182
+ return { layout, version: opencodeBrowserVersion2, current };
183
+ }
143
184
  function autoUpdateOverride(env = process.env) {
144
185
  const value = env.OPENCODE_BROWSER_AUTO_UPDATE?.trim().toLowerCase();
145
186
  if (value === "on" || value === "1" || value === "true")
@@ -224,24 +265,25 @@ var runCommand = (command, args, options = {}) => new Promise((resolve) => {
224
265
  });
225
266
  async function registryUrl(run = runCommand, env = process.env) {
226
267
  const override = env.OPENCODE_BROWSER_NPM_REGISTRY?.trim();
227
- const configured = override || (await run("npm", ["config", "get", "registry"], { timeoutMs: 15000 })).stdout.trim();
268
+ const configured = override || (compiledBinary() ? "" : (await run("npm", ["config", "get", "registry"], { timeoutMs: 15000 })).stdout.trim());
228
269
  return /^https?:\/\//.test(configured) ? configured.replace(/\/+$/, "") : DEFAULT_REGISTRY;
229
270
  }
230
271
  async function fetchPublished(options) {
231
272
  const request = options.fetch ?? fetch;
232
- const response = await request(`${options.registry}/${PACKAGE_NAME.replace("/", "%2f")}`, {
273
+ const name = options.packageName ?? updatePackage();
274
+ const response = await request(`${options.registry}/${name.replace("/", "%2f")}`, {
233
275
  headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" },
234
276
  signal: AbortSignal.timeout(20000)
235
277
  });
236
278
  if (!response.ok)
237
- throw new Error(`the registry answered ${response.status} for ${PACKAGE_NAME}`);
279
+ throw new Error(`the registry answered ${response.status} for ${name}`);
238
280
  const body = await response.json();
239
281
  const version = body["dist-tags"]?.[options.channel];
240
282
  if (!version)
241
- throw new Error(`${PACKAGE_NAME} has no ${options.channel} dist-tag`);
283
+ throw new Error(`${name} has no ${options.channel} dist-tag`);
242
284
  const integrity = body.versions?.[version]?.dist?.integrity;
243
285
  if (!integrity || !/^sha512-/.test(integrity))
244
- throw new Error(`${PACKAGE_NAME}@${version} has no sha512 integrity in the registry`);
286
+ throw new Error(`${name}@${version} has no sha512 integrity in the registry`);
245
287
  return { version, integrity };
246
288
  }
247
289
  function copyRunningPackage(source, prefix) {
@@ -267,6 +309,8 @@ function copyRunningPackage(source, prefix) {
267
309
  copy(source);
268
310
  }
269
311
  async function installVersion(options) {
312
+ if (compiledBinary() || options.runningBinary)
313
+ return installBinaryVersion(options);
270
314
  const run = options.run ?? runCommand;
271
315
  const { layout } = options;
272
316
  const spec = options.spec ?? `${PACKAGE_NAME}@${options.version}`;
@@ -310,6 +354,106 @@ async function installVersion(options) {
310
354
  rmSync(temporary, { recursive: true, force: true });
311
355
  }
312
356
  }
357
+ async function installBinaryVersion(options) {
358
+ const run = options.run ?? runCommand;
359
+ const { layout } = options;
360
+ mkdirSync(layout.versions, { recursive: true });
361
+ const temporary = path2.join(layout.versions, `.install-${process.pid}-${Date.now()}`);
362
+ rmSync(temporary, { recursive: true, force: true });
363
+ mkdirSync(temporary, { recursive: true });
364
+ try {
365
+ const file = path2.join(temporary, binaryName());
366
+ const expected = options.runningBinary ? opencodeBrowserVersion2 : options.version;
367
+ if (options.runningBinary)
368
+ copyFileSync(options.runningBinary, file);
369
+ else {
370
+ const name = updatePackage();
371
+ if (name === PACKAGE_NAME)
372
+ throw new Error("this executable doesn't know its platform package");
373
+ const registry = options.registry ?? await registryUrl(run);
374
+ const integrity = options.integrity ?? await publishedIntegrity(registry, name, options.version);
375
+ const response = await fetch(`${registry}/${name}/-/${name.split("/")[1]}-${options.version}.tgz`, { signal: AbortSignal.timeout(10 * 60000) });
376
+ if (!response.ok)
377
+ throw new Error(`downloading ${name}@${options.version} failed: the registry answered ${response.status}`);
378
+ const tgz = new Uint8Array(await response.arrayBuffer());
379
+ const actual = `sha512-${createHash("sha512").update(tgz).digest("base64")}`;
380
+ if (actual !== integrity)
381
+ throw new Error(`${name}@${options.version} downloaded with integrity ${actual}, not the registry's ${integrity}`);
382
+ const executable = tarFile(tgz, `package/bin/${binaryName()}`);
383
+ if (!executable)
384
+ throw new Error(`${name}@${options.version} has no bin/${binaryName()}`);
385
+ writeFileSync(file, executable);
386
+ }
387
+ chmodSync(file, 493);
388
+ const reported = await run(file, ["--version"], { timeoutMs: 30000, env: { ...process.env, OPENCODE_BROWSER_AUTOSTART: "0" } });
389
+ if (reported.status !== 0 || reported.stdout.trim() !== expected)
390
+ throw new Error(`browse ${expected} didn't run: \`--version\` printed ${JSON.stringify(reported.stdout.trim() || reported.stderr.trim()).slice(0, 200)}`);
391
+ const target = versionDirectory(layout, expected);
392
+ rmSync(target, { recursive: true, force: true });
393
+ renameSync(temporary, target);
394
+ return versionEntry(layout, expected, true);
395
+ } finally {
396
+ rmSync(temporary, { recursive: true, force: true });
397
+ }
398
+ }
399
+ async function publishedIntegrity(registry, name, version) {
400
+ const response = await fetch(`${registry}/${name.replace("/", "%2f")}/${version}`, { signal: AbortSignal.timeout(20000) });
401
+ if (!response.ok)
402
+ throw new Error(`the registry answered ${response.status} for ${name}@${version}`);
403
+ const integrity = (await response.json()).dist?.integrity;
404
+ if (!integrity || !/^sha512-/.test(integrity))
405
+ throw new Error(`${name}@${version} has no sha512 integrity in the registry`);
406
+ return integrity;
407
+ }
408
+ function tarFile(tgz, wanted) {
409
+ const tar = gunzipSync(tgz);
410
+ let offset = 0;
411
+ let longName;
412
+ while (offset + 512 <= tar.length) {
413
+ const header = tar.subarray(offset, offset + 512);
414
+ if (header.every((byte) => byte === 0))
415
+ return;
416
+ const field = (start, length) => header.subarray(start, start + length).toString("utf8").replace(/\0[\s\S]*$/, "");
417
+ const size = parseInt(field(124, 12).trim() || "0", 8);
418
+ const type = field(156, 1) || "0";
419
+ const prefix = field(345, 155);
420
+ const name = longName ?? (prefix ? `${prefix}/${field(0, 100)}` : field(0, 100));
421
+ longName = undefined;
422
+ const body = tar.subarray(offset + 512, offset + 512 + size);
423
+ offset += 512 + Math.ceil(size / 512) * 512;
424
+ if (type === "x")
425
+ longName = /(?:^|\n)\d+ path=([^\n]*)\n/.exec(body.toString("utf8"))?.[1];
426
+ else if (type === "L")
427
+ longName = body.toString("utf8").replace(/\0[\s\S]*$/, "");
428
+ else if (type === "0" && name === wanted)
429
+ return Buffer.from(body);
430
+ }
431
+ return;
432
+ }
433
+ function placeBinary(source, target) {
434
+ const staged = `${target}.${process.pid}.new`;
435
+ rmSync(staged, { force: true });
436
+ try {
437
+ linkSync(source, staged);
438
+ } catch {
439
+ copyFileSync(source, staged);
440
+ }
441
+ chmodSync(staged, 493);
442
+ if (process.platform === "win32") {
443
+ const dir = path2.dirname(target);
444
+ const stale = `${path2.basename(target)}.old-`;
445
+ for (const name of readdirSync(dir)) {
446
+ if (!name.startsWith(stale))
447
+ continue;
448
+ try {
449
+ rmSync(path2.join(dir, name), { force: true });
450
+ } catch {}
451
+ }
452
+ if (existsSync(target))
453
+ renameSync(target, `${target}.old-${Date.now()}`);
454
+ }
455
+ renameSync(staged, target);
456
+ }
313
457
  function processAlive(pid) {
314
458
  if (!Number.isSafeInteger(pid) || pid <= 0)
315
459
  return false;
@@ -344,15 +488,19 @@ function validatePending(layout) {
344
488
  return state;
345
489
  return updateState(layout, ({ pending: _pending, ...rest }) => rest);
346
490
  }
347
- function switchVersion(layout, version, previous) {
348
- if (!existsSync(versionEntry(layout, version)))
491
+ function switchVersion(layout, version, previous, binary = compiledBinary() ? readCurrent(layout)?.binary : undefined) {
492
+ const entry = versionEntry(layout, version, binary !== undefined);
493
+ if (!existsSync(entry))
349
494
  throw new Error(`${version} isn't installed in ${layout.versions}`);
350
- writeCurrent(layout, { version, ...previous && previous !== version ? { previous } : {}, channel: channelOf(version) });
351
- writeShim(layout);
495
+ if (binary && !(version === opencodeBrowserVersion2 && realpath(binary) === realpath(process.execPath)))
496
+ placeBinary(entry, binary);
497
+ writeCurrent(layout, { version, ...previous && previous !== version ? { previous } : {}, channel: channelOf(version), ...binary ? { binary } : {} });
498
+ if (!binary)
499
+ writeShim(layout);
352
500
  }
353
501
  function shimSource() {
354
502
  return `#!/usr/bin/env node
355
- // Browse's stable entry, written by install (cli/src/auto-update.ts). opencode's browse MCP server runs this
503
+ // browse's stable entry, written by install (cli/src/auto-update.ts). opencode's browse MCP server runs this
356
504
  // file, so its configuration never changes when the runtime updates. Don't edit; install rewrites it.
357
505
  import { spawn } from "node:child_process"
358
506
  import { readFileSync } from "node:fs"
@@ -606,7 +754,8 @@ function spawnRuntimeChild(layout, entry, args, onExit, env = process.env) {
606
754
  mkdirSync(path2.join(layout.root, "logs"), { recursive: true });
607
755
  const log = openSync(path2.join(layout.root, "logs", "auto-update.log"), "a", 384);
608
756
  try {
609
- const child = spawn(process.execPath, [entry, ...args], { detached: true, stdio: ["ignore", log, log], env: { ...rest, OPENCODE_BROWSER_HOME: layout.root } });
757
+ const [file, ...prefix] = entryCommand(entry);
758
+ const child = spawn(file, [...prefix, ...args], { detached: true, stdio: ["ignore", log, log], env: { ...rest, OPENCODE_BROWSER_HOME: layout.root } });
610
759
  child.on("error", () => onExit(null));
611
760
  child.on("exit", (code) => onExit(code));
612
761
  child.unref();
@@ -619,115 +768,4 @@ function spawnRuntimeChild(layout, entry, args, onExit, env = process.env) {
619
768
  }
620
769
  }
621
770
 
622
- // src/protocol.ts
623
- var extensionProtocolVersion = 3;
624
- var minExtensionProtocolVersion = 3;
625
- var unpackedExtensionId = "afeafocngkodbmaipcngoamamfmekgfo";
626
- var unpackedReloadAdvice = "Reload Browse on your browser's extensions page (↻).";
627
- var checkoutReloadPrefix = "Extension protocol incompatible: extension-older; reload ";
628
- var extensionReconnectAlarmPeriodMs = 30000;
629
- var legacyExtensionProtocolVersion = 1;
630
- function extensionProtocolCompatibility(value) {
631
- const legacy = value === undefined;
632
- const valid = typeof value === "number" && Number.isSafeInteger(value) && value > 0;
633
- const version = valid ? value : legacy ? legacyExtensionProtocolVersion : null;
634
- return { version, compatible: version !== null && version >= minExtensionProtocolVersion && version <= extensionProtocolVersion, legacy };
635
- }
636
- function extensionProtocolSkew(version) {
637
- if (typeof version !== "number")
638
- return;
639
- if (version > extensionProtocolVersion)
640
- return "extension-newer";
641
- if (version < minExtensionProtocolVersion)
642
- return "extension-older";
643
- return;
644
- }
645
- function extensionProtocolAdvice(version, commands, distribution) {
646
- const skew = extensionProtocolSkew(version);
647
- const versions = `extension protocol ${version ?? "unknown"}, this relay speaks ${minExtensionProtocolVersion === extensionProtocolVersion ? extensionProtocolVersion : `${minExtensionProtocolVersion} to ${extensionProtocolVersion}`}`;
648
- if (skew === "extension-newer")
649
- return `The Browse extension is newer than this browse CLI (${versions}). Update the CLI: run \`${commands.install}\` (an npm install with automatic updates also catches up by itself within a day), then restart opencode.`;
650
- if (skew === "extension-older") {
651
- const store = "The Chrome Web Store updates it within a few hours (to update now, open chrome://extensions, turn on Developer mode and click Update).";
652
- const next = distribution === "unpacked" ? unpackedReloadAdvice : distribution === "store" ? store : `For an unpacked extension: ${unpackedReloadAdvice} For a store extension: ${store}`;
653
- return `The Browse extension is older than this browse CLI (${versions}). ${next}`;
654
- }
655
- return `The Browse extension and this browse CLI don't speak the same protocol (${versions}). Run \`${commands.install}\` again and update the extension.`;
656
- }
657
- function makePageStatus(options) {
658
- return {
659
- state: options.state,
660
- owner: options.targetOwner === "user" ? "user" : "session",
661
- ...options.sessionId ? { sessionId: options.sessionId } : {},
662
- ...options.sessionId && options.readOnly ? { readOnly: true } : {},
663
- ...options.message ? { message: options.message } : {},
664
- ...options.handoffId ? { handoffId: options.handoffId } : {},
665
- ...options.recording ? { recording: true } : {}
666
- };
667
- }
668
- var extensionCommandMethodValues = [
669
- "ping",
670
- "debugger.attach",
671
- "debugger.detach",
672
- "debugger.sendCommand",
673
- "tabs.create",
674
- "tabs.remove",
675
- "tabs.group",
676
- "tabs.ungroup",
677
- "action.setAttached",
678
- "action.setBadge",
679
- "pageStatus.set",
680
- "pageStatus.clear",
681
- "runtime.reload",
682
- "recording.start",
683
- "recording.stop",
684
- "recording.status",
685
- "recording.cancel",
686
- "tabs.cleanup",
687
- "update.notice",
688
- "relay.settings",
689
- "settings.get",
690
- "settings.set"
691
- ];
692
- var extensionEventMethodValues = [
693
- "protocol",
694
- "hello",
695
- "auth.code",
696
- "auth.response",
697
- "ready",
698
- "toolbar.clicked",
699
- "handoff.completed",
700
- "debugger.event",
701
- "debugger.attached",
702
- "debugger.detached",
703
- "tabs.removed",
704
- "tabs.opened",
705
- "pong",
706
- "log",
707
- "recording.cancelled",
708
- "pageStatus.requested",
709
- "settings"
710
- ];
711
- function parseJsonObject(input) {
712
- const parsed = JSON.parse(input);
713
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
714
- throw new Error("Expected JSON object");
715
- }
716
- return parsed;
717
- }
718
- var extensionCommandMethods = new Set(extensionCommandMethodValues);
719
- var extensionEventMethods = new Set(extensionEventMethodValues);
720
- function isCdpRequest(input) {
721
- return typeof input.id === "number" && typeof input.method === "string" && (input.params === undefined || isJsonObject(input.params)) && (input.sessionId === undefined || typeof input.sessionId === "string");
722
- }
723
- function isExtensionResponse(input) {
724
- return typeof input.id === "number" && (input.result === undefined || isJsonObject(input.result)) && (input.error === undefined || typeof input.error === "string") && !(input.result !== undefined && input.error !== undefined);
725
- }
726
- function isExtensionEvent(input) {
727
- return typeof input.method === "string" && extensionEventMethods.has(input.method) && (input.params === undefined || isJsonObject(input.params));
728
- }
729
- function isJsonObject(value) {
730
- return value !== null && typeof value === "object" && !Array.isArray(value);
731
- }
732
-
733
- export { dataRoot, oldStoreMarker, fromCheckout, checkoutRoot, cliCommand, storeConfig, devExtension, devExtensionUrl, devExtensionFolder, storeUrl, installCommand, autoUpdateModes, runtimeLayout, versionEntry, channelOf, UPDATE_VERSION, compareVersions, readCurrent, writeCurrent, updateState, npmRuntime, autoUpdateOverride, storedMode, effectiveMode, setStoredMode, autoUpdateStatus, describeAutoUpdate, registryUrl, fetchPublished, installVersion, pruneVersions, switchVersion, createAutoUpdater, extensionProtocolVersion, minExtensionProtocolVersion, unpackedExtensionId, unpackedReloadAdvice, checkoutReloadPrefix, extensionReconnectAlarmPeriodMs, extensionProtocolCompatibility, extensionProtocolSkew, extensionProtocolAdvice, makePageStatus, parseJsonObject, isCdpRequest, isExtensionResponse, isExtensionEvent, isJsonObject };
771
+ export { dataRoot, oldStoreMarker, fromCheckout, compiledBinary, binaryPackage, scriptEntry, entryCommand, runningEntry, checkoutRoot, cliCommand, storeConfig, devExtension, devExtensionUrl, devExtensionFolder, storeUrl, installCommand, autoUpdateModes, runtimeLayout, versionEntry, channelOf, UPDATE_VERSION, compareVersions, readCurrent, writeCurrent, updateState, npmRuntime, autoUpdateOverride, storedMode, effectiveMode, setStoredMode, autoUpdateStatus, describeAutoUpdate, registryUrl, fetchPublished, installVersion, pruneVersions, switchVersion, createAutoUpdater };
@@ -1,16 +1,16 @@
1
- import {
2
- dataRoot,
3
- cliCommand
4
- } from "./main-ma25hhqh.mjs";
5
1
  import {
6
2
  parseTargetSelection2,
7
3
  getObject2,
8
4
  getString2
9
- } from "./main-0nta0a2z.mjs";
5
+ } from "./main-xwrjyf0g.mjs";
10
6
  import {
11
7
  isNodeError,
12
8
  writeJsonFileAtomically
13
- } from "./main-20ahaz2q.mjs";
9
+ } from "./main-z53824j2.mjs";
10
+ import {
11
+ dataRoot,
12
+ cliCommand
13
+ } from "./main-drvnesx3.mjs";
14
14
 
15
15
  // src/relay/user-settings.ts
16
16
  var settingKeys = ["window", "grouping", "idleMinutes", "cursor", "humanInput", "handoffNotifications", "autoUpdate"];
@@ -145,7 +145,7 @@ function browsersNote2(browsers) {
145
145
  return ` Connected browsers: ${list}. Pass browser to use one other than the default.`;
146
146
  }
147
147
  function mcpUpdateNote2(notice) {
148
- return `Note: Browse has an update: ${notice.summary}. Tell the user they can run \`${notice.command}\` and then restart opencode, or ask you to run it for them.`;
148
+ return `Note: browse has an update: ${notice.summary}. Tell the user they can run \`${notice.command}\` and then restart opencode, or ask you to run it for them.`;
149
149
  }
150
150
  function objectSchema(properties, required = []) {
151
151
  return {
@@ -410,7 +410,7 @@ function errorCode(code) {
410
410
  }
411
411
  var errorHints = {
412
412
  locked: "Deadbolt is locked, so agent sign-ins are paused until the user unlocks it.",
413
- forbidden: "Deadbolt doesn't know this Browse install (its client was removed or revoked). Ask the user to run `opencode-browser deadbolt connect`.",
413
+ forbidden: "Deadbolt doesn't know this browse install (its client was removed or revoked). Ask the user to run `opencode-browser deadbolt connect`.",
414
414
  rate_limited: "Deadbolt is rate-limiting this client; wait before trying again."
415
415
  };
416
416
  function rawRequest(socket, request, timeoutMs = requestTimeoutMs) {
@@ -502,7 +502,7 @@ function toLogin(raw) {
502
502
  function connect(options = {}) {
503
503
  const client = options.client ?? readClient(options.root);
504
504
  if (!client) {
505
- throw new DeadboltError("not_connected", "Browse isn't connected to Deadbolt. Ask the user to run `opencode-browser deadbolt connect` and to allow the logins agents may use with `deadbolt agents allow <site>`.");
505
+ throw new DeadboltError("not_connected", "browse isn't connected to Deadbolt. Ask the user to run `opencode-browser deadbolt connect` and to allow the logins agents may use with `deadbolt agents allow <site>`.");
506
506
  }
507
507
  const socket = options.socket ?? socketPath();
508
508
  let protocol;
@@ -604,7 +604,7 @@ function pickLogin(logins, site, username, field) {
604
604
  if (candidates.length === 1)
605
605
  return candidates[0];
606
606
  if (candidates.length === 0) {
607
- throw new DeadboltError("not_granted", username === undefined ? `No Deadbolt login is granted to Browse for ${site}${field ? ` (${field})` : ""}. Ask the user to allow one with \`deadbolt agents allow ${site}\`.` : `No Deadbolt login for ${username} is granted to Browse for ${site}${field ? ` (${field})` : ""}. Ask the user to allow it with \`deadbolt agents allow ${site} --login ${username}\`.`, { site, ...username ? { login: username } : {} });
607
+ throw new DeadboltError("not_granted", username === undefined ? `No Deadbolt login is granted to browse for ${site}${field ? ` (${field})` : ""}. Ask the user to allow one with \`deadbolt agents allow ${site}\`.` : `No Deadbolt login for ${username} is granted to browse for ${site}${field ? ` (${field})` : ""}. Ask the user to allow it with \`deadbolt agents allow ${site} --login ${username}\`.`, { site, ...username ? { login: username } : {} });
608
608
  }
609
609
  throw new DeadboltError("failed", `Several Deadbolt logins are granted for ${site}; name one: deadbolt:${site}/<username> (${candidates.map((login) => login.username ?? login.title).join(", ")})`);
610
610
  }
@@ -1,12 +1,15 @@
1
1
  import {
2
2
  dataRoot,
3
+ compiledBinary,
3
4
  checkoutRoot,
4
5
  cliCommand,
5
6
  compareVersions,
6
7
  npmRuntime,
7
- autoUpdateStatus,
8
+ autoUpdateStatus
9
+ } from "./main-drvnesx3.mjs";
10
+ import {
8
11
  unpackedReloadAdvice
9
- } from "./main-ma25hhqh.mjs";
12
+ } from "./main-3gthmkzw.mjs";
10
13
 
11
14
  // src/update.ts
12
15
  import { execFile, spawnSync } from "node:child_process";
@@ -227,7 +230,7 @@ function runUpdate(args, deps) {
227
230
  };
228
231
  const repo = deps.repo;
229
232
  if (!repo) {
230
- log("Browse was installed from npm. Update it with:");
233
+ log("browse was installed from npm. Update it with:");
231
234
  log(" npx @opencode/browse@latest install");
232
235
  log("then restart opencode.");
233
236
  return 0;
@@ -312,9 +315,14 @@ function runUpdate(args, deps) {
312
315
  async function runUpdateCommand2(args) {
313
316
  const log = (line) => process.stdout.write(line + `
314
317
  `);
315
- const { runNpmUpdate } = await import("./runtime-update-baranjc5.mjs");
318
+ const { runNpmUpdate } = await import("./runtime-update-3nekhds7.mjs");
316
319
  if (await runNpmUpdate(args))
317
320
  return;
321
+ if (compiledBinary()) {
322
+ log(`This browse isn't installed. Run \`${cliCommand("install")}\` to set it up; it updates itself from then on.`);
323
+ process.exitCode = 1;
324
+ return;
325
+ }
318
326
  if (args.includes("--check")) {
319
327
  const repo = checkoutRoot();
320
328
  if (!repo)
@@ -0,0 +1,4 @@
1
+ // src/version.ts
2
+ var opencodeBrowserVersion2 = "0.0.6-dev.22";
3
+ var opencodeBrowserBuildId2 = "2026-11-04T18:37:35.454Z";
4
+ export { opencodeBrowserVersion2, opencodeBrowserBuildId2 };