From b6459730d6950eee1571ab8148c454fbdbf3b54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=BE=99=E5=AE=89?= <297888591+wanglongan587@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:42:16 +0800 Subject: [PATCH 1/3] build: support integrity-pinned npm CLI archives --- .github/workflows/ci.yml | 3 ++ deno.json | 2 +- scripts/package.ts | 78 +++++++++++++++++++++++++++++++--------- scripts/sync-upstream.ts | 11 ++++-- scripts/upstream.test.ts | 22 ++++++++++++ scripts/upstream.ts | 63 ++++++++++++++++++++++++++++++-- 6 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 scripts/upstream.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16d35da..adee045 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,3 +31,6 @@ jobs: # step above cannot run. - name: Lint run: deno task lint + + - name: Archive integrity tests + run: deno test scripts/upstream.test.ts diff --git a/deno.json b/deno.json index 39856fd..090b4a7 100644 --- a/deno.json +++ b/deno.json @@ -20,7 +20,7 @@ "dev": "deno run --no-prompt --allow-run --allow-read --allow-env --allow-net src/main.ts", "build": "deno bundle src/main.ts -o dist/main.js", "sync": "deno run --allow-net --allow-read --allow-write --allow-env scripts/sync-upstream.ts", - "package": "deno run --allow-run --allow-read --allow-write --allow-env scripts/package.ts" + "package": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net=registry.npmjs.org scripts/package.ts" }, "compilerOptions": { "strict": true, diff --git a/scripts/package.ts b/scripts/package.ts index 36a39d5..303ba3b 100644 --- a/scripts/package.ts +++ b/scripts/package.ts @@ -31,7 +31,7 @@ import { basename, dirname, join, relative } from "@std/path"; import { UntarStream } from "@std/tar"; import { BlobReader, ZipReader, ZipWriter } from "@zip-js/zip-js"; import bundle from "../bundle.config.ts"; -import { LOCK_PATH, readLock } from "./upstream.ts"; +import { LOCK_PATH, readLock, verifyIntegrity } from "./upstream.ts"; /** Operating systems a package can be built for, as `Deno.build.os` spells them. */ export type TargetOs = typeof Deno.build.os; @@ -51,6 +51,8 @@ export type BundleConfig = cli: "bundled"; /** GitHub `owner/name` the CLI is released from. */ upstream: string; + /** An explicitly selected npm distribution; never resolves latest at package time. */ + npm?: { package: string; version: string }; /** Release asset serving each canonical Rust target triple. */ assets: Record; /** @@ -299,40 +301,60 @@ async function buildBundledPackage( plan: TargetPlan, upstream: string, upstreamTag: string, - expectedSha256: string, + expectedSha256: string | undefined, fileName: string, includeDeclaration: boolean, + npmAsset?: { url: string; integrity: string }, ): Promise { await Deno.remove(STAGE_DIR, { recursive: true }).catch(() => {}); const staged = join(STAGE_DIR, plan.binaryPath); await Deno.mkdir(dirname(staged), { recursive: true }); - const archive = join(DOWNLOAD_DIR, plan.asset); - await run( - "gh", - "release", - "download", - upstreamTag, - "--repo", - upstream, - "--pattern", - plan.asset, - "--dir", + const archive = join( DOWNLOAD_DIR, - "--clobber", + npmAsset ? `${plan.triple}.tgz` : plan.asset, ); + if (npmAsset) { + const response = await fetch(npmAsset.url, { + signal: AbortSignal.timeout(180_000), + }); + if (!response.ok) throw new Error(`download failed: ${response.status}`); + const bytes = new Uint8Array(await response.arrayBuffer()); + await verifyIntegrity(bytes, npmAsset.integrity); + await Deno.writeFile(archive, bytes); + } else { + await run( + "gh", + "release", + "download", + upstreamTag, + "--repo", + upstream, + "--pattern", + plan.asset, + "--dir", + DOWNLOAD_DIR, + "--clobber", + ); + } // Verified against the digest `deno task sync` recorded from GitHub's own Releases API, the // same role npm's `dist.integrity` plays for the sibling `claude-code-agent` and `codex-agent` // — this catches upstream replacing an asset under a tag this plugin already pinned. - const actualSha256 = await sha256Hex(archive); - if (actualSha256 !== expectedSha256) { + const actualSha256 = npmAsset ? undefined : await sha256Hex(archive); + if (!npmAsset && actualSha256 !== expectedSha256) { throw new Error( `${plan.asset} sha256 ${actualSha256} does not match ${LOCK_PATH}'s pinned ${expectedSha256}; run 'deno task sync'`, ); } // Upstream ships the CLI as the sole entry at the archive root, under the same name this // package uses for it. - await extractEntry(archive, basename(plan.binaryPath), staged); + await extractEntry( + archive, + npmAsset + ? `package/bin/${basename(plan.binaryPath)}` + : basename(plan.binaryPath), + staged, + ); await stagePluginFiles(plan.triple, includeDeclaration); await writeOrax(STAGE_DIR, join(PACKAGES_DIR, fileName), plan.binaryPath); @@ -393,6 +415,18 @@ async function buildRelease( // shipped, and a "latest" lookup at package time could straddle an upstream release and ship a // version skew a rebuild would not repeat. const lock = await readLock(); + if ( + config.npm && + (lock.npm?.package !== config.npm.package || + lock.npm.version !== config.npm.version) + ) { + throw new Error( + "npm configuration differs from upstream.lock.json; run 'deno task sync'", + ); + } + if (!config.npm && lock.npm) { + throw new Error("unexpected npm lock for GitHub configuration"); + } console.log(`Bundling ${lock.repo} ${lock.tag}`); for (const [triple, asset] of Object.entries(config.assets)) { @@ -407,6 +441,15 @@ async function buildRelease( `${LOCK_PATH} pins ${pinned.name} for ${triple}, but bundle.config.ts now names ${asset}; run 'deno task sync'`, ); } + if (config.npm ? !pinned.npm : !pinned.sha256 || pinned.npm) { + throw new Error(`missing or mismatched integrity source for ${triple}`); + } + if ( + pinned.npm && + new URL(pinned.npm.url).origin !== "https://registry.npmjs.org" + ) { + throw new Error(`unexpected npm download origin for ${triple}`); + } const plan: TargetPlan = { triple, asset, @@ -420,6 +463,7 @@ async function buildRelease( pinned.sha256, fileName, includeDeclaration, + pinned.npm, ); const path = join(PACKAGES_DIR, fileName); diff --git a/scripts/sync-upstream.ts b/scripts/sync-upstream.ts index ddfaf0c..b2243c4 100644 --- a/scripts/sync-upstream.ts +++ b/scripts/sync-upstream.ts @@ -4,6 +4,7 @@ import bundle from "../bundle.config.ts"; import { LOCK_PATH, readLock, + resolveNpmUpstream, resolveUpstream, type UpstreamLock, writeLock, @@ -27,7 +28,9 @@ async function main(): Promise { } const before = await currentLock(); - const resolved = await resolveUpstream(bundle.upstream, bundle.assets); + const resolved = bundle.npm + ? await resolveNpmUpstream(bundle.upstream, bundle.npm, bundle.assets) + : await resolveUpstream(bundle.upstream, bundle.assets); const unchanged = before !== undefined && JSON.stringify(before) === JSON.stringify(resolved); const summary = `${resolved.repo}@${resolved.tag}`; @@ -46,7 +49,11 @@ async function main(): Promise { await writeLock(resolved); console.log(`${LOCK_PATH} now pins ${summary}`); for (const [triple, asset] of Object.entries(resolved.assets)) { - console.log(` ${triple}: ${asset.name} sha256:${asset.sha256}`); + console.log( + ` ${triple}: ${asset.name} ${ + asset.npm?.integrity ?? `sha256:${asset.sha256}` + }`, + ); } } diff --git a/scripts/upstream.test.ts b/scripts/upstream.test.ts new file mode 100644 index 0000000..f435653 --- /dev/null +++ b/scripts/upstream.test.ts @@ -0,0 +1,22 @@ +import { verifyIntegrity } from "./upstream.ts"; + +Deno.test("npm archives must match the pinned SHA-512 before extraction", async () => { + const bytes = new TextEncoder().encode("release archive"); + const digest = new Uint8Array(await crypto.subtle.digest("SHA-512", bytes)); + const integrity = `sha512-${btoa(String.fromCharCode(...digest))}`; + await verifyIntegrity(bytes, integrity); + for ( + const [content, sri] of [ + [new TextEncoder().encode("replaced archive"), integrity], + [bytes, "sha256-not-a-sha512-pin"], + ] as const + ) { + let rejected = false; + try { + await verifyIntegrity(content, sri); + } catch { + rejected = true; + } + if (!rejected) throw new Error("unverified archive was accepted"); + } +}); diff --git a/scripts/upstream.ts b/scripts/upstream.ts index 521d460..0b945e7 100644 --- a/scripts/upstream.ts +++ b/scripts/upstream.ts @@ -14,15 +14,17 @@ export const LOCK_PATH = "upstream.lock.json"; export interface PinnedAsset { /** The release asset's file name, matching `bundle.config.ts`'s `assets` entry for this target. */ name: string; - size: number; - sha256: string; + size?: number; + sha256?: string; + npm?: { url: string; integrity: string }; } export interface UpstreamLock { /** GitHub `owner/name` the CLI is released from. */ repo: string; tag: string; - publishedAt: string; + publishedAt?: string; + npm?: { package: string; version: string }; /** Keyed by target triple, matching `bundle.config.ts`'s `assets`. */ assets: Record; } @@ -105,3 +107,58 @@ export async function resolveUpstream( assets, }; } + +/** Verifies the registry's SHA-512 SRI before any archive is extracted. */ +export async function verifyIntegrity( + bytes: Uint8Array, + integrity: string, +): Promise { + if (!/^sha512-[A-Za-z0-9+/]{86}==$/.test(integrity)) { + throw new Error("invalid npm SHA-512 integrity"); + } + const hash = new Uint8Array(await crypto.subtle.digest("SHA-512", bytes)); + const actual = `sha512-${btoa(String.fromCharCode(...hash))}`; + if (actual !== integrity) throw new Error("npm archive integrity mismatch"); +} + +/** Pins one explicit npm version and its per-platform archives, without following latest. */ +export async function resolveNpmUpstream( + repo: string, + npm: { package: string; version: string }, + assetsByTriple: Record, +): Promise { + const read = async (name: string): Promise<{ + name: string; + version: string; + optionalDependencies?: Record; + dist: { tarball: string; integrity: string }; + }> => { + const response = await fetch( + `https://registry.npmjs.org/${encodeURIComponent(name)}/${ + encodeURIComponent(npm.version) + }`, + ); + if (!response.ok) { + throw new Error(`npm ${name}@${npm.version}: ${response.status}`); + } + return await response.json(); + }; + const parent = await read(npm.package); + const assets: Record = {}; + for (const [triple, name] of Object.entries(assetsByTriple)) { + if (parent.optionalDependencies?.[name] !== npm.version) { + throw new Error(`unlisted platform ${name}`); + } + const metadata = await read(name); + const { tarball: url, integrity } = metadata.dist; + if ( + metadata.name !== name || metadata.version !== npm.version || + new URL(url).origin !== "https://registry.npmjs.org" || + !/^sha512-[A-Za-z0-9+/]{86}==$/.test(integrity) + ) { + throw new Error(`invalid npm metadata for ${name}`); + } + assets[triple] = { name, npm: { url, integrity } }; + } + return { repo, tag: npm.version, npm, assets }; +} From 415c2b8f5d71935d88dc5d8534f1e3df36932253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=BE=99=E5=AE=89?= <297888591+wanglongan587@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:42:16 +0800 Subject: [PATCH 2/3] fix: start OpenCode ACP with host working directory --- src/handlers/models.ts | 2 +- src/services/opencode-client.ts | 3 ++- tests/host-simulator.ts | 38 ++++++++++++++++++++++++++------- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/handlers/models.ts b/src/handlers/models.ts index 7b13cdd..685f06c 100644 --- a/src/handlers/models.ts +++ b/src/handlers/models.ts @@ -122,7 +122,7 @@ async function discoverModels( // Resolution failures — no CLI on PATH, a bundled one that cannot run — are raised here as the // classified errors Ora acts on, before any probe exists to blur them into a timeout. const child = await spawnOpenCode(processes, { - args: ["acp", "--cwd", cwd], + args: ["acp"], cwd, }); const probe = AcpProbe.attach(child); diff --git a/src/services/opencode-client.ts b/src/services/opencode-client.ts index f7f45f2..f23aaa6 100644 --- a/src/services/opencode-client.ts +++ b/src/services/opencode-client.ts @@ -96,7 +96,8 @@ export class OpenCodeClient { // stays retryable, while a package that cannot run the one it ships does not. let process: SpawnedProcess; try { - process = await this.#spawn(["acp", "--cwd", cwd], cwd); + // V2 removed --cwd; the host-owned process cwd supplies the same directory. + process = await this.#spawn(["acp"], cwd); } catch (error) { log.warn("the CLI could not be spawned", { context: { cwd }, error }); throw error; diff --git a/tests/host-simulator.ts b/tests/host-simulator.ts index 65c50a7..8259338 100644 --- a/tests/host-simulator.ts +++ b/tests/host-simulator.ts @@ -5,7 +5,8 @@ * deno run --allow-run --allow-read --allow-env --allow-net tests/host-simulator.ts */ import type { JsonValue } from "@ora-space/plugin-sdk"; -import { fromFileUrl } from "@std/path"; +import { join } from "@std/path"; +import { parseArgs } from "@std/cli/parse-args"; const JSON_RPC_FRAME_TYPE = 0x01; const MAX_FRAME_LENGTH = 16 * 1024 * 1024; @@ -90,7 +91,12 @@ function modulePath(relative: string): string { .replace(/^\/([A-Za-z]:)/, "$1"); } -const entrypoint = modulePath("../src/main.ts"); +const flags = parseArgs(Deno.args, { string: ["package-root", "cwd"] }); +const packageRoot = flags["package-root"] ?? modulePath("../"); +const workspace = flags.cwd ?? Deno.cwd(); +const entrypoint = flags["package-root"] + ? join(packageRoot, "main.js") + : modulePath("../src/main.ts"); const child = new Deno.Command(Deno.execPath(), { args: ["run", ...HOST_PERMISSIONS, entrypoint], stdin: "piped", @@ -248,8 +254,7 @@ function resolveSimulatedProgram(params: Record): string { if (packageCommand === undefined) { return params.command as string; } - const packageRoot = new URL("../", import.meta.url); - const resolved = fromFileUrl(new URL(packageCommand, packageRoot)); + const resolved = join(packageRoot, packageCommand); const stat = statOrUndefined(resolved); if (stat === undefined) { throw new SimulatedSpawnError( @@ -289,6 +294,7 @@ async function dispatchChildProcessMethod( child = new Deno.Command(command, { args, cwd, + env: params.env as Record | undefined, stdin: "piped", stdout: "piped", stderr: "piped", @@ -416,9 +422,12 @@ await send({ jsonrpc: "2.0", id: 1, method: "agent/start", - params: { cwd: Deno.cwd(), hostVersion: "0.8.0" }, + params: { cwd: workspace, hostVersion: "0.8.0" }, }); const started = await waitFor((message) => message.id === 1, "agent/start"); +if (started.error) { + throw new Error(`agent/start: ${JSON.stringify(started.error)}`); +} console.log( `ok: agent/start ${JSON.stringify(started.result ?? started.error)}`, ); @@ -440,6 +449,9 @@ const initialized = await waitFor( (message) => message.method === "agent/acp" && acpFrame(message).id === 1, "ACP initialize", ); +if (acpFrame(initialized).error) { + throw new Error(`initialize: ${JSON.stringify(acpFrame(initialized).error)}`); +} console.log( `ok: initialize ${ JSON.stringify(acpFrame(initialized).result).slice(0, 200) @@ -453,13 +465,16 @@ await send({ jsonrpc: "2.0", id: 2, method: "session/new", - params: { cwd: Deno.cwd(), mcpServers: [] }, + params: { cwd: workspace, mcpServers: [] }, }, }); const session = await waitFor( (message) => message.method === "agent/acp" && acpFrame(message).id === 2, "ACP session/new", ); +if (acpFrame(session).error) { + throw new Error(`session/new: ${JSON.stringify(acpFrame(session).error)}`); +} console.log( `ok: session/new ${ JSON.stringify(acpFrame(session).result ?? acpFrame(session).error).slice( @@ -476,7 +491,7 @@ await send({ jsonrpc: "2.0", id: 2, method: "agent/list_models", - params: { cwd: Deno.cwd() }, + params: { cwd: workspace }, }); const models = await waitFor( (message) => message.id === 2, @@ -584,13 +599,20 @@ await send({ jsonrpc: "2.0", id: 3, method: "session/new", - params: { cwd: Deno.cwd(), mcpServers: [] }, + params: { cwd: workspace, mcpServers: [] }, }, }); const sessionAfterRestart = await waitFor( (message) => message.method === "agent/acp" && acpFrame(message).id === 3, "ACP session/new after restart", ); +if (acpFrame(sessionAfterRestart).error) { + throw new Error( + `session/new after restart: ${ + JSON.stringify(acpFrame(sessionAfterRestart).error) + }`, + ); +} console.log( `ok: session/new after restart ${ JSON.stringify( From 4cc6d860c82a91d296f1d81fcc92e9f7783b3120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=BE=99=E5=AE=89?= <297888591+wanglongan587@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:42:16 +0800 Subject: [PATCH 3/3] build: pin OpenCode 2.0.32 experimental packages --- README.md | 44 ++++++++++++++++++++++++++++++++++++++------ bundle.config.ts | 9 +++++---- orax.toml | 2 +- upstream.lock.json | 39 +++++++++++++++++++++++++-------------- 4 files changed, 69 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 9e55660..d16b134 100644 --- a/README.md +++ b/README.md @@ -79,13 +79,23 @@ cmd /c mklink /J "\plugins" "%USERPROFILE%\.ora\plugins\installed" ## Building from source +This prerelease pins OpenCode **2.0.32** from the official `@opencode/cli` npm +distribution. `bundle.config.ts` selects an explicit version and platform +packages; `deno task sync` records their tarball URLs and SHA-512 integrity in +`upstream.lock.json`. Packaging verifies integrity before extracting +`package/bin/opencode[.exe]`; it never resolves a newer version. The GitHub +Release source remains supported for configurations without `npm`. + ``` +deno task sync --check deno task build -deno task package --tag v0.2.4 --repo ora-space/opencode-agent +deno task package --tag v0.7.0-opencode2.1 --repo ora-space/opencode-agent ``` This produces one `.orax` package per target platform, with the matching -OpenCode CLI bundled inside. `gh` is the only external tool required. +OpenCode CLI bundled inside. npm packaging downloads from `registry.npmjs.org`; +GitHub-based configurations additionally require `gh`. Generated download URLs +describe a future release; building packages does not upload or publish them. `bundle.config.ts` decides which of the two release shapes is built, and it is the only file to change to switch: @@ -124,6 +134,18 @@ for a package that bundles no CLI, and the run exercises the `PATH` fallback against your own OpenCode instead. `deno task check` type checks and `deno task lint` lints the sources. +`deno test scripts/upstream.test.ts` checks archive integrity rejection. + +To exercise the bundled entrypoint as well as the CLI, extract a complete +package to a separate directory and run: + +``` +deno task simulate --package-root /absolute/extracted-package --cwd /absolute/workspace +``` + +The simulator fails on ACP error responses and covers startup, model discovery, +Effect coordination/restart/readiness and stop. It does not install the package +into a user's Ora profile or replace the marketplace version. ### SDK @@ -151,9 +173,19 @@ shows up the next time the picker is opened. ## Known limits -- Each model discovery leaves one empty session behind in OpenCode's own session - list. OpenCode advertises `session/close`, `session/fork`, `session/list`, and - `session/resume`, but not `session/delete`, so the probe has nothing to clean - up with; the five-minute cache is what keeps the count down. +- OpenCode v2 supports `session/delete`, which model discovery uses to remove + its temporary session. Older CLI overrides without that capability leave an + empty session behind. +- V2 runs as `opencode acp` with the host-provided working directory; it does + not accept the v1 `--cwd` flag. +- The plugin transparently forwards child-session metadata. Complete child + details after reload depend on the host preserving that metadata in history. + This prerelease does not implement host history or UI changes and does not + enable the separate `opencode/child-session-updates` extension. +- Nested subagents require OpenCode's `experimental.subagent_depth` to permit + the requested depth. The plugin does not change the user's depth policy. +- Interactive question forms require an ACP client supporting the corresponding + elicitation capability. With no such capability, OpenCode reports that the + question could not be shown; ordinary permission requests remain separate. - Killing the CLI on agent stop is best effort; Ora retains process-tree reaping as a backstop. diff --git a/bundle.config.ts b/bundle.config.ts index 13d9a85..c8213a3 100644 --- a/bundle.config.ts +++ b/bundle.config.ts @@ -18,11 +18,12 @@ import { bundledBinaryPath } from "./src/services/bundled-binary.ts"; export default { cli: "bundled", upstream: "anomalyco/opencode", + npm: { package: "@opencode/cli", version: "2.0.32" }, assets: { - "aarch64-apple-darwin": "opencode-darwin-arm64.zip", - "x86_64-unknown-linux-gnu": "opencode-linux-x64.tar.gz", - "aarch64-unknown-linux-gnu": "opencode-linux-arm64.tar.gz", - "x86_64-pc-windows-msvc": "opencode-windows-x64.zip", + "aarch64-apple-darwin": "@opencode/cli-darwin-arm64", + "x86_64-unknown-linux-gnu": "@opencode/cli-linux-x64", + "aarch64-unknown-linux-gnu": "@opencode/cli-linux-arm64", + "x86_64-pc-windows-msvc": "@opencode/cli-windows-x64", }, binaryPath: bundledBinaryPath, } satisfies BundleConfig; diff --git a/orax.toml b/orax.toml index 1e92bb7..712b1ee 100644 --- a/orax.toml +++ b/orax.toml @@ -3,7 +3,7 @@ title = "OpenCode" identifier = "ora-space.opencode" namespace = "official" kind = "agent" -version = "0.6.11" +version = "0.7.0-opencode2.1" description = "Ora Space OpenCode Agent" homepage = "https://github.com/ora-space/opencode-agent" license = "Apache-2.0" diff --git a/upstream.lock.json b/upstream.lock.json index 80ad557..3938064 100644 --- a/upstream.lock.json +++ b/upstream.lock.json @@ -1,27 +1,38 @@ { "repo": "anomalyco/opencode", - "tag": "v1.19.3", - "publishedAt": "2026-10-16T12:31:52Z", + "tag": "2.0.32", + "npm": { + "package": "@opencode/cli", + "version": "2.0.32" + }, "assets": { "aarch64-apple-darwin": { - "name": "opencode-darwin-arm64.zip", - "size": 45587801, - "sha256": "c2d0fecd27acf6e08be02c1f5d384614d07495587766bcc4a6bff3971749bb64" + "name": "@opencode/cli-darwin-arm64", + "npm": { + "url": "https://registry.npmjs.org/@opencode/cli-darwin-arm64/-/cli-darwin-arm64-2.0.32.tgz", + "integrity": "sha512-wASwXBsWaOAYcZrh4ZLl4oaVxPCUAGVb7zZvXBBF8TjWE4uWgY0D2tj6gn80JfcO3b/s9woM6dP/gKqYKK2EdQ==" + } }, "x86_64-unknown-linux-gnu": { - "name": "opencode-linux-x64.tar.gz", - "size": 60716380, - "sha256": "13a8fb2978302071b86f9d7e00da9c8895f9cd846978379c3bdc6a5333536c9b" + "name": "@opencode/cli-linux-x64", + "npm": { + "url": "https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-2.0.32.tgz", + "integrity": "sha512-Q50ofBQ3gdwjD6ZQhrVR8fVlm8D2xPMshy4dynY6l6b+qLdKgBSJ9WpoaDyrQX1YWmf2s7IN4RbXmoXXdJitDA==" + } }, "aarch64-unknown-linux-gnu": { - "name": "opencode-linux-arm64.tar.gz", - "size": 60519745, - "sha256": "7900818a6a98881edbbadab4b7525940eff3b9750040c200481f9f56cacee405" + "name": "@opencode/cli-linux-arm64", + "npm": { + "url": "https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-2.0.32.tgz", + "integrity": "sha512-YR5O03irvmot/G0f8W49ehvhn5uqCFN3vf9JTsChvf47W6ldjKCNvsaCSXS2SP26dPOUMQBn7WqbKnyHzrNR5w==" + } }, "x86_64-pc-windows-msvc": { - "name": "opencode-windows-x64.zip", - "size": 62204138, - "sha256": "e4ce9117f55fc068414513789f8c7d1049f855d7518bb4e350b9170a3a20d8c3" + "name": "@opencode/cli-windows-x64", + "npm": { + "url": "https://registry.npmjs.org/@opencode/cli-windows-x64/-/cli-windows-x64-2.0.32.tgz", + "integrity": "sha512-5VdKolj2/NkSk3BuM4qeiYpnNnGRT7szmd2RIv3+aSDPMwD9yRySYkJ2f1WIM7dTDfO5lZHAXVoipt6+i6AD4A==" + } } } }