node v2 cli support (#36309)
This commit is contained in:
parent
27e1692848
commit
a1b274e6f8
75 changed files with 1502 additions and 367 deletions
|
|
@ -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>` }
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue