From f145d8040ebcfca182c329d63e7910b161440cb7 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Fri, 10 Jul 2026 09:36:21 +0200 Subject: [PATCH] feat(cli): add Node runtime bootstrap --- packages/cli/src/node/assets.node.ts | 17 ++++++ packages/cli/src/node/index.ts | 3 + packages/cli/src/node/opentui-native.ts | 4 ++ .../cli/src/node/plugin-runtime.effect.ts | 28 +++++++++ .../cli/src/node/plugin-runtime.promise.ts | 26 ++++++++ packages/cli/src/node/target.ts | 61 +++++++++++++++++++ 6 files changed, 139 insertions(+) create mode 100644 packages/cli/src/node/assets.node.ts create mode 100644 packages/cli/src/node/index.ts create mode 100644 packages/cli/src/node/opentui-native.ts create mode 100644 packages/cli/src/node/plugin-runtime.effect.ts create mode 100644 packages/cli/src/node/plugin-runtime.promise.ts create mode 100644 packages/cli/src/node/target.ts diff --git a/packages/cli/src/node/assets.node.ts b/packages/cli/src/node/assets.node.ts new file mode 100644 index 0000000000..7fa42dd864 --- /dev/null +++ b/packages/cli/src/node/assets.node.ts @@ -0,0 +1,17 @@ +import path from "node:path" +import { fileURLToPath } from "node:url" +import { webTreeSitterWasmAsset } from "./target" + +const root = process.env.OPENCODE_NODE_ASSETS_DIR ?? fileURLToPath(new URL("./assets/", import.meta.url)) + +export function resolveNodeAsset(asset: string) { + return path.join(root, asset) +} + +export function resolveOpenTuiAsset(relative: string) { + return resolveNodeAsset(`@opentui/core/${relative}`) +} + +export function resolveWebTreeSitterWasm() { + return resolveNodeAsset(webTreeSitterWasmAsset) +} diff --git a/packages/cli/src/node/index.ts b/packages/cli/src/node/index.ts new file mode 100644 index 0000000000..b7bb7db541 --- /dev/null +++ b/packages/cli/src/node/index.ts @@ -0,0 +1,3 @@ +import "./plugin-runtime.promise" +import "./plugin-runtime.effect" +import "../index" diff --git a/packages/cli/src/node/opentui-native.ts b/packages/cli/src/node/opentui-native.ts new file mode 100644 index 0000000000..f7aa38cdc0 --- /dev/null +++ b/packages/cli/src/node/opentui-native.ts @@ -0,0 +1,4 @@ +import { opentuiNativeAsset } from "./target" +import { resolveNodeAsset } from "./assets.node" + +export default await resolveNodeAsset(opentuiNativeAsset) diff --git a/packages/cli/src/node/plugin-runtime.effect.ts b/packages/cli/src/node/plugin-runtime.effect.ts new file mode 100644 index 0000000000..5e38b00d4c --- /dev/null +++ b/packages/cli/src/node/plugin-runtime.effect.ts @@ -0,0 +1,28 @@ +import { + Agent, + Command, + Connection, + Credential, + Integration, + Model, + Plugin, + Provider, + Reference, + Skill, +} from "@opencode-ai/plugin/v2/effect" +import { Tool } from "@opencode-ai/plugin/v2/effect/tool" + +const key = Symbol.for("opencode.plugin.v2.effect") +;(globalThis as typeof globalThis & { [key]?: unknown })[key] = { + Agent, + Command, + Connection, + Credential, + Integration, + Model, + Plugin, + Provider, + Reference, + Skill, + Tool, +} diff --git a/packages/cli/src/node/plugin-runtime.promise.ts b/packages/cli/src/node/plugin-runtime.promise.ts new file mode 100644 index 0000000000..93e20b87bb --- /dev/null +++ b/packages/cli/src/node/plugin-runtime.promise.ts @@ -0,0 +1,26 @@ +import { + Agent, + Command, + Connection, + Credential, + Integration, + Model, + Plugin, + Provider, + Reference, + Skill, +} from "@opencode-ai/plugin/v2" + +const key = Symbol.for("opencode.plugin.v2.promise") +;(globalThis as typeof globalThis & { [key]?: unknown })[key] = { + Agent, + Command, + Connection, + Credential, + Integration, + Model, + Plugin, + Provider, + Reference, + Skill, +} diff --git a/packages/cli/src/node/target.ts b/packages/cli/src/node/target.ts new file mode 100644 index 0000000000..80d4adc30f --- /dev/null +++ b/packages/cli/src/node/target.ts @@ -0,0 +1,61 @@ +const nativeLibraries = { + darwin: "libopentui.dylib", + linux: "libopentui.so", + win32: "opentui.dll", +} as const + +export type NodeTarget = ReturnType + +export function nodeTarget(platform: string, arch: string) { + if (!(platform in nativeLibraries) || (arch !== "arm64" && arch !== "x64")) { + throw new Error(`Unsupported Node executable target: ${platform}-${arch}`) + } + + const targetPlatform = platform as keyof typeof nativeLibraries + const targetArch = arch as "arm64" | "x64" + const opentuiNativePackage = `@opentui/core-${targetPlatform}-${targetArch}` + const nodePtyPackage = `@lydell/node-pty-${targetPlatform}-${targetArch}` + const parcelWatcherPackage = `@parcel/watcher-${targetPlatform}-${targetArch}${targetPlatform === "linux" ? "-glibc" : ""}` + + return { + platform: targetPlatform, + arch: targetArch, + opentuiNativePackage, + opentuiNativeAsset: `${opentuiNativePackage}/${nativeLibraries[targetPlatform]}`, + nodePtyPackage, + nodePtyEntryAsset: `${nodePtyPackage}/lib/index.js`, + parcelWatcherPackage, + parcelWatcherAsset: `${parcelWatcherPackage}/watcher.node`, + } +} + +const target = nodeTarget(process.platform, process.arch) + +export const opentuiNativePackage = target.opentuiNativePackage +export const opentuiNativeAsset = target.opentuiNativeAsset +export const opentuiParserWorkerAsset = "@opentui/core/parser.worker.js" +export const webTreeSitterWasmAsset = "web-tree-sitter/tree-sitter.wasm" +export const photonWasmAsset = "@silvia-odwyer/photon-node/photon_rs_bg.wasm" +export const nodeExecArgv = ["--experimental-ffi", "--use-system-ca", "--disable-warning=ExperimentalWarning"] as const + +export const opentuiParserAssets = [ + "@opentui/core/assets/javascript/highlights.scm", + "@opentui/core/assets/javascript/tree-sitter-javascript.wasm", + "@opentui/core/assets/typescript/highlights.scm", + "@opentui/core/assets/typescript/tree-sitter-typescript.wasm", + "@opentui/core/assets/markdown/highlights.scm", + "@opentui/core/assets/markdown/tree-sitter-markdown.wasm", + "@opentui/core/assets/markdown/injections.scm", + "@opentui/core/assets/markdown_inline/highlights.scm", + "@opentui/core/assets/markdown_inline/tree-sitter-markdown_inline.wasm", + "@opentui/core/assets/zig/highlights.scm", + "@opentui/core/assets/zig/tree-sitter-zig.wasm", +] as const + +export const attentionSoundAssets = [ + "@opencode-ai/ui/audio/bip-bop-01.mp3", + "@opencode-ai/ui/audio/bip-bop-03.mp3", + "@opencode-ai/ui/audio/staplebops-06.mp3", + "@opencode-ai/ui/audio/nope-03.mp3", + "@opencode-ai/ui/audio/yup-01.mp3", +] as const