node v2 cli support (#36309)
This commit is contained in:
parent
27e1692848
commit
a1b274e6f8
75 changed files with 1502 additions and 367 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { EOL } from "node:os"
|
||||
import path from "node:path"
|
||||
import { readFile, stat, writeFile } from "node:fs/promises"
|
||||
import { Effect, Option } from "effect"
|
||||
import { applyEdits, modify } from "jsonc-parser"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
|
|
@ -35,7 +36,7 @@ export default Runtime.handler(
|
|||
}),
|
||||
)
|
||||
|
||||
async function resolveConfigPath(directory: string) {
|
||||
export async function resolveConfigPath(directory: string) {
|
||||
const candidates = [
|
||||
path.join(directory, "opencode.json"),
|
||||
path.join(directory, "opencode.jsonc"),
|
||||
|
|
@ -43,16 +44,24 @@ async function resolveConfigPath(directory: string) {
|
|||
path.join(directory, ".opencode", "opencode.jsonc"),
|
||||
]
|
||||
for (const candidate of candidates) {
|
||||
if (await Bun.file(candidate).exists()) return candidate
|
||||
if (
|
||||
await stat(candidate).then(
|
||||
(info) => info.isFile(),
|
||||
() => false,
|
||||
)
|
||||
)
|
||||
return candidate
|
||||
}
|
||||
return candidates[0]
|
||||
}
|
||||
|
||||
async function write(configPath: string, name: string, server: unknown) {
|
||||
const file = Bun.file(configPath)
|
||||
const text = (await file.exists()) ? await file.text() : "{}"
|
||||
const text = await readFile(configPath, "utf8").catch((error) => {
|
||||
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return "{}"
|
||||
throw error
|
||||
})
|
||||
const edits = modify(text, ["mcp", "servers", name], server, {
|
||||
formattingOptions: { tabSize: 2, insertSpaces: true },
|
||||
})
|
||||
await Bun.write(configPath, applyEdits(text, edits))
|
||||
await writeFile(configPath, applyEdits(text, edits))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useTerminalDimensions } from "@opentui/solid"
|
|||
import { createEffect, createMemo, createSignal, type Accessor } from "solid-js"
|
||||
import { transparent, type RunFooterTheme } from "./theme"
|
||||
import { Locale } from "@opencode-ai/tui/util/locale"
|
||||
import { stringWidth } from "@opencode-ai/tui/util/string-width"
|
||||
|
||||
export const FOOTER_MENU_ROWS = 8
|
||||
|
||||
|
|
@ -196,7 +197,7 @@ export function RunFooterMenu(props: {
|
|||
...props
|
||||
.items()
|
||||
.filter((item) => item.description)
|
||||
.map((item) => Bun.stringWidth(item.display)),
|
||||
.map((item) => stringWidth(item.display)),
|
||||
)
|
||||
return width === 0 ? 0 : width + 2
|
||||
})
|
||||
|
|
@ -205,14 +206,14 @@ export function RunFooterMenu(props: {
|
|||
return ""
|
||||
}
|
||||
|
||||
return " ".repeat(Math.max(1, descriptionColumn() - Bun.stringWidth(item.display)))
|
||||
return " ".repeat(Math.max(1, descriptionColumn() - stringWidth(item.display)))
|
||||
}
|
||||
const descriptionText = (item: RunFooterMenuItem) => {
|
||||
if (!item.description) {
|
||||
return
|
||||
}
|
||||
|
||||
const footerWidth = item.footer ? Bun.stringWidth(item.footer) + 1 : 0
|
||||
const footerWidth = item.footer ? stringWidth(item.footer) + 1 : 0
|
||||
const available =
|
||||
term().width -
|
||||
(border() ? 1 : 0) -
|
||||
|
|
|
|||
|
|
@ -5,14 +5,15 @@
|
|||
// It produces a PromptState that RunPromptBody renders as a slim single-line
|
||||
// composer while the footer view renders any active menus below it.
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { pathToFileURL } from "bun"
|
||||
import { StyledText, fg, type ColorInput, type KeyEvent, type TextareaRenderable } from "@opentui/core"
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import { normalizePromptContent } from "@opencode-ai/tui/prompt/content"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "node:url"
|
||||
import { createEffect, createMemo, createResource, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
|
||||
import { Locale } from "@opencode-ai/tui/util/locale"
|
||||
import { stringWidth } from "@opencode-ai/tui/util/string-width"
|
||||
import {
|
||||
createPromptHistory,
|
||||
displayCharAt,
|
||||
|
|
@ -602,7 +603,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|||
})
|
||||
}
|
||||
|
||||
const restore = (value: RunPrompt, cursor = Bun.stringWidth(value.text)) => {
|
||||
const restore = (value: RunPrompt, cursor = stringWidth(value.text)) => {
|
||||
draft = clonePrompt(value)
|
||||
setShell(value.mode === "shell")
|
||||
if (!area || area.isDestroyed) {
|
||||
|
|
@ -612,7 +613,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|||
hide()
|
||||
area.setText(value.text)
|
||||
restoreParts(value.parts)
|
||||
area.cursorOffset = Math.min(cursor, Bun.stringWidth(area.plainText))
|
||||
area.cursorOffset = Math.min(cursor, stringWidth(area.plainText))
|
||||
scheduleRows()
|
||||
area.focus()
|
||||
}
|
||||
|
|
@ -643,7 +644,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|||
area.setText(text)
|
||||
clearParts()
|
||||
draft = shell() ? { text: area.plainText, parts: [], mode: "shell" } : { text: area.plainText, parts: [] }
|
||||
area.cursorOffset = Math.min(Bun.stringWidth(text), Bun.stringWidth(area.plainText))
|
||||
area.cursorOffset = Math.min(stringWidth(text), stringWidth(area.plainText))
|
||||
scheduleRows()
|
||||
area.focus()
|
||||
}
|
||||
|
|
@ -777,7 +778,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|||
if (move(dir, event)) return
|
||||
if (!area || area.isDestroyed) return false
|
||||
|
||||
const endOffset = Bun.stringWidth(area.plainText)
|
||||
const endOffset = stringWidth(area.plainText)
|
||||
if (dir === -1) {
|
||||
if (area.cursorOffset === 0) return false
|
||||
if (area.visualCursor.visualRow === 0) {
|
||||
|
|
@ -886,16 +887,12 @@ export function createPromptState(input: PromptInput): PromptState {
|
|||
area.cursorOffset = 0
|
||||
const start = area.logicalCursor
|
||||
area.cursorOffset =
|
||||
shell() || !head
|
||||
? cursor
|
||||
: local
|
||||
? Bun.stringWidth(area.plainText)
|
||||
: Bun.stringWidth(area.plainText.slice(0, head.end))
|
||||
shell() || !head ? cursor : local ? stringWidth(area.plainText) : stringWidth(area.plainText.slice(0, head.end))
|
||||
const end = area.logicalCursor
|
||||
|
||||
area.deleteRange(start.row, start.col, end.row, end.col)
|
||||
area.insertText(text)
|
||||
area.cursorOffset = Bun.stringWidth(text)
|
||||
area.cursorOffset = stringWidth(text)
|
||||
hide()
|
||||
syncDraft()
|
||||
if (!shell()) {
|
||||
|
|
@ -920,7 +917,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|||
|
||||
const text = "@" + next.value
|
||||
const startOffset = at()
|
||||
const endOffset = startOffset + Bun.stringWidth(text)
|
||||
const endOffset = startOffset + stringWidth(text)
|
||||
const part = structuredClone(next.part)
|
||||
if (part.type === "agent") {
|
||||
part.source = {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { ServerConnection } from "../services/server-connection"
|
|||
import { waitForCatalogReady } from "./catalog.shared"
|
||||
import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./runtime.stdin"
|
||||
import type { RunInput, RunTuiConfig } from "./types"
|
||||
import { readStdin } from "../util/io"
|
||||
import { setTimeout } from "node:timers/promises"
|
||||
|
||||
export type MiniCommandInput = {
|
||||
server: ServerConnection.Resolved
|
||||
|
|
@ -22,7 +24,7 @@ export type MiniCommandInput = {
|
|||
type Session = Awaited<ReturnType<OpenCodeClient["session"]["get"]>>
|
||||
export async function runMini(input: MiniCommandInput) {
|
||||
validate(input)
|
||||
const initialInput = mergeInput(process.stdin.isTTY ? undefined : await Bun.stdin.text(), input.prompt)
|
||||
const initialInput = mergeInput(process.stdin.isTTY ? undefined : await readStdin(), input.prompt)
|
||||
const runtimeTask = import("./runtime")
|
||||
const directory = localDirectory()
|
||||
|
||||
|
|
@ -123,7 +125,7 @@ async function validateAgent(sdk: OpenCodeClient, directory: string, name?: stri
|
|||
return
|
||||
}
|
||||
if (agent) return name
|
||||
await Bun.sleep(25)
|
||||
await setTimeout(25)
|
||||
}
|
||||
if (!agents) {
|
||||
warning("failed to list agents. Falling back to default agent")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise"
|
||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import { EOL } from "node:os"
|
||||
import { readFile } from "node:fs/promises"
|
||||
import { UI } from "./ui"
|
||||
import type { MiniToolPart } from "./types"
|
||||
|
||||
|
|
@ -478,11 +479,11 @@ async function prepareFile(file: File) {
|
|||
if (file.mime !== "text/plain") {
|
||||
const uri = file.url.startsWith("data:")
|
||||
? file.url
|
||||
: `data:${file.mime};base64,${Buffer.from(await Bun.file(new URL(file.url)).arrayBuffer()).toString("base64")}`
|
||||
: `data:${file.mime};base64,${(await readFile(new URL(file.url))).toString("base64")}`
|
||||
return { attachment: { uri, mime: file.mime, name: file.filename } }
|
||||
}
|
||||
const content = file.url.startsWith("data:")
|
||||
? Buffer.from(file.url.slice(file.url.indexOf(",") + 1), "base64").toString("utf8")
|
||||
: await Bun.file(new URL(file.url)).text()
|
||||
: await readFile(new URL(file.url), "utf8")
|
||||
return { text: `<file name="${file.filename}">\n${content}\n</file>` }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
// the current draft is saved and history begins. Arrowing past the end
|
||||
// restores the draft.
|
||||
export { displayCharAt, displaySlice, mentionTriggerIndex } from "@opencode-ai/tui/prompt/display"
|
||||
import { stringWidth } from "@opencode-ai/tui/util/string-width"
|
||||
import type { RunPrompt } from "./types"
|
||||
|
||||
const HISTORY_LIMIT = 200
|
||||
|
|
@ -102,7 +103,7 @@ export function movePromptHistory(state: PromptHistoryState, dir: -1 | 1, text:
|
|||
return { state, apply: false }
|
||||
}
|
||||
|
||||
if (dir === 1 && cursor !== Bun.stringWidth(text)) {
|
||||
if (dir === 1 && cursor !== stringWidth(text)) {
|
||||
return { state, apply: false }
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +137,7 @@ export function movePromptHistory(state: PromptHistoryState, dir: -1 | 1, text:
|
|||
index: null,
|
||||
},
|
||||
text: state.draft,
|
||||
cursor: Bun.stringWidth(state.draft),
|
||||
cursor: stringWidth(state.draft),
|
||||
apply: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -147,7 +148,7 @@ export function movePromptHistory(state: PromptHistoryState, dir: -1 | 1, text:
|
|||
index: idx,
|
||||
},
|
||||
text: state.items[idx].text,
|
||||
cursor: dir === -1 ? 0 : Bun.stringWidth(state.items[idx].text),
|
||||
cursor: dir === -1 ? 0 : stringWidth(state.items[idx].text),
|
||||
apply: true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
|||
import { Model } from "@opencode-ai/schema/model"
|
||||
import { open } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { readStdin } from "../util/io"
|
||||
import { ServerConnection } from "../services/server-connection"
|
||||
import { loadRunAgents, waitForCatalogReady } from "./catalog.shared"
|
||||
import { runNonInteractivePrompt } from "./noninteractive"
|
||||
|
|
@ -48,7 +49,7 @@ async function run(input: RunCommandInput) {
|
|||
if (input.fork && !input.continue && !input.session) fail("--fork requires --continue or --session")
|
||||
const root = process.env.PWD ?? process.cwd()
|
||||
const directory = localDirectory(root)
|
||||
const message = mergeInput(formatMessage(input.message), process.stdin.isTTY ? undefined : await Bun.stdin.text())
|
||||
const message = mergeInput(formatMessage(input.message), process.stdin.isTTY ? undefined : await readStdin())
|
||||
if (!message?.trim()) fail("You must provide a message")
|
||||
const files = await Promise.all(input.file.map((file) => prepareFile(file, root)))
|
||||
const prepared = { directory, message, files }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { readFile } from "node:fs/promises"
|
||||
import type {
|
||||
EventSubscribeOutput,
|
||||
OpenCodeClient,
|
||||
|
|
@ -161,7 +162,7 @@ async function prepareFile(file: RunFilePart) {
|
|||
if (file.mime !== "text/plain") return { attachment: { uri: file.url, name: file.filename } }
|
||||
const content = file.url.startsWith("data:")
|
||||
? Buffer.from(file.url.slice(file.url.indexOf(",") + 1), "base64").toString("utf8")
|
||||
: await Bun.file(new URL(file.url)).text()
|
||||
: await readFile(new URL(file.url), "utf8")
|
||||
return { text: `<file name="${file.filename}">\n${content}\n</file>` }
|
||||
}
|
||||
|
||||
|
|
|
|||
3
packages/cli/src/node/index.ts
Normal file
3
packages/cli/src/node/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import "./plugin-runtime.promise"
|
||||
import "./plugin-runtime.effect"
|
||||
import "../index"
|
||||
28
packages/cli/src/node/plugin-runtime.effect.ts
Normal file
28
packages/cli/src/node/plugin-runtime.effect.ts
Normal file
|
|
@ -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,
|
||||
}
|
||||
26
packages/cli/src/node/plugin-runtime.promise.ts
Normal file
26
packages/cli/src/node/plugin-runtime.promise.ts
Normal file
|
|
@ -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,
|
||||
}
|
||||
34
packages/cli/src/node/target.ts
Normal file
34
packages/cli/src/node/target.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
const platforms = ["darwin", "linux", "win32"] as const
|
||||
|
||||
export type NodeTarget = ReturnType<typeof nodeTarget>
|
||||
|
||||
export function nodeTarget(platform: string, arch: string) {
|
||||
if (!platforms.includes(platform as (typeof platforms)[number]) || (arch !== "arm64" && arch !== "x64")) {
|
||||
throw new Error(`Unsupported Node executable target: ${platform}-${arch}`)
|
||||
}
|
||||
|
||||
const targetPlatform = platform as (typeof platforms)[number]
|
||||
const targetArch = arch as "arm64" | "x64"
|
||||
const nodePtyPackage = `@lydell/node-pty-${targetPlatform}-${targetArch}`
|
||||
const parcelWatcherPackage = `@parcel/watcher-${targetPlatform}-${targetArch}${targetPlatform === "linux" ? "-glibc" : ""}`
|
||||
|
||||
return {
|
||||
platform: targetPlatform,
|
||||
arch: targetArch,
|
||||
nodePtyPackage,
|
||||
nodePtyEntryAsset: `${nodePtyPackage}/lib/index.js`,
|
||||
parcelWatcherPackage,
|
||||
parcelWatcherAsset: `${parcelWatcherPackage}/watcher.node`,
|
||||
}
|
||||
}
|
||||
|
||||
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 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
|
||||
|
|
@ -5,6 +5,7 @@ import { Service } from "@opencode-ai/client/effect/service"
|
|||
import { Effect, FileSystem, Option, Schema } from "effect"
|
||||
import { randomBytes } from "crypto"
|
||||
import path from "path"
|
||||
import { selfCommand } from "../util/process"
|
||||
|
||||
// The CLI's service configuration file, plus the Service.EnsureOptions binding that
|
||||
// points the client package's service operations at this CLI: which
|
||||
|
|
@ -78,13 +79,10 @@ const paths = Effect.gen(function* () {
|
|||
export const options = Effect.fnUntraced(function* () {
|
||||
const { file, legacyFile } = yield* paths
|
||||
yield* migrateRegistration(legacyFile, file)
|
||||
const compiled = path.basename(process.execPath).replace(/\.exe$/, "") !== "bun"
|
||||
const entrypoint = compiled ? undefined : process.argv[1]
|
||||
if (!compiled && entrypoint === undefined) return yield* Effect.fail(new Error("Failed to resolve CLI entrypoint"))
|
||||
return {
|
||||
file,
|
||||
version: InstallationVersion,
|
||||
command: [process.execPath, ...(entrypoint ? [entrypoint] : []), "serve", "--service"],
|
||||
command: [...selfCommand(), "serve", "--service"],
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|||
import { Effect, Schema, Stream } from "effect"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import { randomBytes } from "node:crypto"
|
||||
import path from "node:path"
|
||||
import { selfCommand } from "../util/process"
|
||||
|
||||
const Ready = Schema.Struct({ url: Schema.String })
|
||||
const decodeReady = Schema.decodeUnknownPromise(Schema.fromJsonString(Ready))
|
||||
|
|
@ -14,10 +14,7 @@ type Options = {
|
|||
}
|
||||
|
||||
function command(password: string, options: Options) {
|
||||
const compiled = path.basename(process.execPath).replace(/\.exe$/, "") !== "bun"
|
||||
const entrypoint = compiled ? [] : process.argv[1] ? [process.argv[1]] : []
|
||||
if (!compiled && entrypoint.length === 0) throw new Error("Failed to resolve CLI entrypoint")
|
||||
const [executable, ...args] = options.command ?? [process.execPath, ...entrypoint, "serve"]
|
||||
const [executable, ...args] = options.command ?? [...selfCommand(), "serve"]
|
||||
if (!executable) throw new Error("Failed to resolve standalone server command")
|
||||
return ChildProcess.make(executable, [...args, "--stdio", "--port", "0"], {
|
||||
cwd: process.cwd(),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
|||
import { registerOpencodeSpinner } from "@opencode-ai/tui/component/register-spinner"
|
||||
import { SPINNER_FRAMES } from "@opencode-ai/tui/component/spinner"
|
||||
import { go } from "@opencode-ai/tui/logo"
|
||||
import { setTimeout } from "node:timers/promises"
|
||||
import {
|
||||
batch,
|
||||
createEffect,
|
||||
|
|
@ -123,7 +124,7 @@ async function open(from?: string): Promise<Session> {
|
|||
let shownAt = performance.now()
|
||||
const waitForStage = async () => {
|
||||
const remaining = stageFloor - (performance.now() - shownAt)
|
||||
if (remaining > 0) await Bun.sleep(remaining)
|
||||
if (remaining > 0) await setTimeout(remaining)
|
||||
}
|
||||
const advance = async (stage: number) => {
|
||||
await waitForStage()
|
||||
|
|
@ -140,11 +141,11 @@ async function open(from?: string): Promise<Session> {
|
|||
setOutcome(next)
|
||||
const completed = await Promise.race([
|
||||
settled.promise.then(() => true),
|
||||
Bun.sleep(transitionDuration + 500).then(() => false),
|
||||
setTimeout(transitionDuration + 500).then(() => false),
|
||||
])
|
||||
resolveOutcome = undefined
|
||||
setAnimating(false)
|
||||
if (completed) await Bun.sleep(hold)
|
||||
if (completed) await setTimeout(hold)
|
||||
}
|
||||
let closing: Promise<void> | undefined
|
||||
let transferred = false
|
||||
|
|
@ -154,7 +155,7 @@ async function open(from?: string): Promise<Session> {
|
|||
setAnimating(false)
|
||||
if (renderer.isDestroyed) return
|
||||
renderer.pause()
|
||||
await Promise.race([renderer.idle(), Bun.sleep(500)])
|
||||
await Promise.race([renderer.idle(), setTimeout(500)])
|
||||
renderer.destroy()
|
||||
})())
|
||||
let loading: Promise<void> | undefined
|
||||
|
|
@ -178,7 +179,7 @@ async function open(from?: string): Promise<Session> {
|
|||
renderer.screenMode = "alternate-screen"
|
||||
renderer.consoleMode = "console-overlay"
|
||||
renderer.requestRender()
|
||||
await Promise.race([renderer.idle(), Bun.sleep(500)])
|
||||
await Promise.race([renderer.idle(), setTimeout(500)])
|
||||
transferred = true
|
||||
return {
|
||||
renderer,
|
||||
|
|
|
|||
|
|
@ -12,11 +12,16 @@ import { parse, type ParseError } from "jsonc-parser"
|
|||
import path from "node:path"
|
||||
import semver from "semver"
|
||||
|
||||
declare const OPENCODE_CLI_NAME: string | undefined
|
||||
|
||||
export type Policy = boolean | "notify"
|
||||
export type Action = "none" | "upgrade"
|
||||
type Method = "npm" | "pnpm" | "bun" | "yarn"
|
||||
|
||||
const packageName = "@opencode-ai/cli"
|
||||
const packageName =
|
||||
typeof OPENCODE_CLI_NAME === "string" && OPENCODE_CLI_NAME === "opencode2-node"
|
||||
? OPENCODE_CLI_NAME
|
||||
: "@opencode-ai/cli"
|
||||
|
||||
export interface Interface {
|
||||
readonly check: () => Effect.Effect<void>
|
||||
|
|
|
|||
5
packages/cli/src/util/io.ts
Normal file
5
packages/cli/src/util/io.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { text } from "node:stream/consumers"
|
||||
|
||||
export function readStdin() {
|
||||
return text(process.stdin)
|
||||
}
|
||||
26
packages/cli/src/util/process.ts
Normal file
26
packages/cli/src/util/process.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import path from "node:path"
|
||||
|
||||
export function selfCommand() {
|
||||
const runtime = path.basename(process.execPath, path.extname(process.execPath)).toLowerCase()
|
||||
if (runtime !== "bun" && runtime !== "node" && runtime !== "nodejs") return [process.execPath]
|
||||
if (!process.argv[1]) throw new Error("Failed to resolve CLI entrypoint")
|
||||
if (runtime === "node" || runtime === "nodejs") return [process.execPath, ...nodeFlags(), process.argv[1]]
|
||||
return [process.execPath, process.argv[1]]
|
||||
}
|
||||
|
||||
function nodeFlags() {
|
||||
return process.execArgv.flatMap((arg, index, args) => {
|
||||
if (index > 0 && args[index - 1] === "--conditions") return []
|
||||
if (arg === "--conditions") return args[index + 1] ? [arg, args[index + 1]] : []
|
||||
if (arg.startsWith("--conditions=")) return [arg]
|
||||
if (
|
||||
arg === "--experimental-ffi" ||
|
||||
arg === "--use-system-ca" ||
|
||||
arg === "--enable-source-maps" ||
|
||||
arg === "--no-addons"
|
||||
)
|
||||
return [arg]
|
||||
if (arg === "--no-warnings" || arg.startsWith("--disable-warning=")) return [arg]
|
||||
return []
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue