From 2015fbad7745dc5166a3573406a9636d03bae856 Mon Sep 17 00:00:00 2001 From: bcode Date: Sun, 3 May 2026 03:46:50 +0000 Subject: [PATCH 1/2] feat(installation): enable curl-based self-upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-enables `bcode upgrade` and the TUI auto-upgrade-on-launch flow for curl-installed users. The hosted `https://bcode.sh/install` script is re-run with VERSION=, which writes the new binary to ~/.bcode/bin/bcode in place — same flow as the original install. Changes from the previous BCODE_UPGRADE_DISABLED stub: - `method()`: only returns "curl" (~/.bcode/bin or ~/.local/bin) or "unknown". The package-manager detection logic for npm/brew/scoop/choco is removed, since BrowserCode doesn't publish to those registries yet and running those checks would either be no-ops or (worse) match an installed `opencode-ai` and try to upgrade the wrong binary. - `latest()`: GitHub releases lookup repointed to `browser-use/browsercode`. For non-curl methods returns the current version so the TUI auto-upgrade check stays silent for devs running from source. - `upgrade()`: curl branch only. Other methods return a clear error pointing at https://bcode.sh/install. Net effect for users: `bcode upgrade` works, and the TUI notifies on new releases (gated by config.autoupdate, same as upstream). When we ship to npm/brew/etc., we'll re-introduce per-method branches from upstream `anomalyco/opencode`. --- packages/opencode/src/installation/index.ts | 209 +++----------------- 1 file changed, 24 insertions(+), 185 deletions(-) diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index dc5b2dc59..b4e06e3a7 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -11,7 +11,6 @@ import * as Log from "@opencode-ai/core/util/log" import { makeRuntime } from "@opencode-ai/core/effect/runtime" import semver from "semver" import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version" -import { NpmConfig } from "@opencode-ai/core/npm-config" const log = Log.create({ service: "installation" }) @@ -69,17 +68,12 @@ export class UpgradeFailedError extends Schema.TaggedErrorClass Effect.Effect @@ -114,40 +108,15 @@ export const layer: Layer.Layer Effect.succeed("")), ) - const run = Effect.fnUntraced( - function* (cmd: string[], opts?: { cwd?: string; env?: Record }) { - const proc = ChildProcess.make(cmd[0], cmd.slice(1), { - cwd: opts?.cwd, - env: opts?.env, - extendEnv: true, - }) - const handle = yield* spawner.spawn(proc) - const [stdout, stderr] = yield* Effect.all( - [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], - { concurrency: 2 }, - ) - const code = yield* handle.exitCode - return { code, stdout, stderr } - }, - Effect.scoped, - Effect.catch(() => Effect.succeed({ code: ChildProcessSpawner.ExitCode(1), stdout: "", stderr: "" })), - ) - - const getBrewFormula = Effect.fnUntraced(function* () { - const tapFormula = yield* text(["brew", "list", "--formula", "anomalyco/tap/opencode"]) - if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode" - const coreFormula = yield* text(["brew", "list", "--formula", "opencode"]) - if (coreFormula.includes("opencode")) return "opencode" - return "opencode" - }) - + // Re-runs the hosted install script with VERSION=, which writes + // the new binary to ~/.bcode/bin/bcode in place. Same flow as the + // user's original `curl https://bcode.sh/install | bash` install. const upgradeCurl = Effect.fnUntraced( function* (target: string) { const response = yield* httpOk.execute(HttpClientRequest.get("https://bcode.sh/install")) const body = yield* response.text - const bodyBytes = new TextEncoder().encode(body) const proc = ChildProcess.make("bash", [], { - stdin: Stream.make(bodyBytes), + stdin: Stream.make(new TextEncoder().encode(body)), env: { VERSION: target }, extendEnv: true, }) @@ -163,15 +132,6 @@ export const layer: Layer.Layer Effect.Effect }> = [ - { name: "npm", command: () => text(["npm", "list", "-g", "--depth=0"]) }, - { name: "yarn", command: () => text(["yarn", "global", "list"]) }, - { name: "pnpm", command: () => text(["pnpm", "list", "-g", "--depth=0"]) }, - { name: "bun", command: () => text(["bun", "pm", "ls", "-g"]) }, - { name: "brew", command: () => text(["brew", "list", "--formula", "opencode"]) }, - { name: "scoop", command: () => text(["scoop", "list", "opencode"]) }, - { name: "choco", command: () => text(["choco", "list", "--limit-output", "opencode"]) }, - ] - - checks.sort((a, b) => { - const aMatches = exec.includes(a.name) - const bMatches = exec.includes(b.name) - if (aMatches && !bMatches) return -1 - if (!aMatches && bMatches) return 1 - return 0 - }) - - for (const check of checks) { - const output = yield* check.command() - const installedName = - check.name === "brew" || check.name === "choco" || check.name === "scoop" ? "opencode" : "opencode-ai" - if (output.includes(installedName)) { - return check.name - } - } - return "unknown" as Method }), latest: Effect.fn("Installation.latest")(function* (installMethod?: Method) { - if (BCODE_UPGRADE_DISABLED) { - return InstallationVersion - } const detectedMethod = installMethod || (yield* result.method()) - - if (detectedMethod === "brew") { - const formula = yield* getBrewFormula() - if (formula.includes("/")) { - const infoJson = yield* text(["brew", "info", "--json=v2", formula]) - const info = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(BrewInfoV2))(infoJson) - return info.formulae[0].versions.stable - } - const response = yield* httpOk.execute( - HttpClientRequest.get("https://formulae.brew.sh/api/formula/opencode.json").pipe( - HttpClientRequest.acceptJson, - ), - ) - const data = yield* HttpClientResponse.schemaBodyJson(BrewFormula)(response) - return data.versions.stable - } - - if (detectedMethod === "npm" || detectedMethod === "bun" || detectedMethod === "pnpm") { - const response = yield* httpOk.execute( - HttpClientRequest.get( - `${yield* NpmConfig.registry(process.cwd())}/opencode-ai/${InstallationChannel}`, - ).pipe(HttpClientRequest.acceptJson), - ) - const data = yield* HttpClientResponse.schemaBodyJson(NpmPackage)(response) - return data.version - } - - if (detectedMethod === "choco") { - const response = yield* httpOk.execute( - HttpClientRequest.get( - "https://community.chocolatey.org/api/v2/Packages?$filter=Id%20eq%20%27opencode%27%20and%20IsLatestVersion&$select=Version", - ).pipe(HttpClientRequest.setHeaders({ Accept: "application/json;odata=verbose" })), - ) - const data = yield* HttpClientResponse.schemaBodyJson(ChocoPackage)(response) - return data.d.results[0].Version - } - - if (detectedMethod === "scoop") { - const response = yield* httpOk.execute( - HttpClientRequest.get( - "https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/opencode.json", - ).pipe(HttpClientRequest.setHeaders({ Accept: "application/json" })), - ) - const data = yield* HttpClientResponse.schemaBodyJson(ScoopManifest)(response) - return data.version - } - + // No-op for unsupported methods so the TUI auto-upgrade check stays + // silent for devs running from source. + if (detectedMethod !== "curl") return InstallationVersion const response = yield* httpOk.execute( - HttpClientRequest.get("https://api.github.com/repos/anomalyco/opencode/releases/latest").pipe( + HttpClientRequest.get("https://api.github.com/repos/browser-use/browsercode/releases/latest").pipe( HttpClientRequest.acceptJson, ), ) @@ -274,67 +161,19 @@ export const layer: Layer.Layer Date: Sun, 3 May 2026 17:46:41 +0000 Subject: [PATCH 2/2] fix(installation): point upgrade failures at the actual one-liner Both UpgradeFailedError raise-sites now show: curl -fsSL https://bcode.sh/install | bash For the non-curl method path: replaces the prose hint with the runnable command. For the curl path that exited non-zero: keeps the install script's own stderr (useful diagnostic) and appends the one-liner as a fallback hint. --- packages/opencode/src/installation/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index b4e06e3a7..967c989ac 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -165,13 +165,14 @@ export const layer: Layer.Layer