summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorSebastian <[email protected]>2026-03-29 00:27:27 +0100
committerGitHub <[email protected]>2026-03-29 00:27:27 +0100
commit38af99dcb47e92e4f25c7aa6344b5f8f9b766e1e (patch)
tree299af567105120803426ffa200c11a08ab69f8ed /packages
parent772059acb5555f958ef616a3ff00a2fd89b36933 (diff)
downloadopencode-38af99dcb47e92e4f25c7aa6344b5f8f9b766e1e.tar.gz
opencode-38af99dcb47e92e4f25c7aa6344b5f8f9b766e1e.zip
prompt slot (#19563)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/package.json4
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx29
-rw-r--r--packages/opencode/src/cli/cmd/tui/plugin/api.tsx14
-rw-r--r--packages/opencode/src/cli/cmd/tui/routes/home.tsx27
-rw-r--r--packages/opencode/test/fixture/tui-plugin.ts1
-rw-r--r--packages/plugin/package.json8
-rw-r--r--packages/plugin/src/tui.ts17
7 files changed, 76 insertions, 24 deletions
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index ca1ecc48a..5dca50be3 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -102,8 +102,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "2.3.3",
- "@opentui/core": "0.1.91",
- "@opentui/solid": "0.1.91",
+ "@opentui/core": "0.1.92",
+ "@opentui/solid": "0.1.92",
"@parcel/watcher": "2.5.1",
"@pierre/diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
index d88747e4c..96563b884 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
@@ -45,6 +45,10 @@ export type PromptProps = {
ref?: (ref: PromptRef) => void
hint?: JSX.Element
showPlaceholder?: boolean
+ placeholders?: {
+ normal?: string[]
+ shell?: string[]
+ }
}
export type PromptRef = {
@@ -57,13 +61,16 @@ export type PromptRef = {
submit(): void
}
-const PLACEHOLDERS = ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"]
-const SHELL_PLACEHOLDERS = ["ls -la", "git status", "pwd"]
const money = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
})
+function randomIndex(count: number) {
+ if (count <= 0) return 0
+ return Math.floor(Math.random() * count)
+}
+
export function Prompt(props: PromptProps) {
let input: TextareaRenderable
let anchor: BoxRenderable
@@ -83,6 +90,8 @@ export function Prompt(props: PromptProps) {
const renderer = useRenderer()
const { theme, syntax } = useTheme()
const kv = useKV()
+ const list = createMemo(() => props.placeholders?.normal ?? [])
+ const shell = createMemo(() => props.placeholders?.shell ?? [])
function promptModelWarning() {
toast.show({
@@ -152,7 +161,7 @@ export function Prompt(props: PromptProps) {
interrupt: number
placeholder: number
}>({
- placeholder: Math.floor(Math.random() * PLACEHOLDERS.length),
+ placeholder: randomIndex(list().length),
prompt: {
input: "",
parts: [],
@@ -166,7 +175,7 @@ export function Prompt(props: PromptProps) {
on(
() => props.sessionID,
() => {
- setStore("placeholder", Math.floor(Math.random() * PLACEHOLDERS.length))
+ setStore("placeholder", randomIndex(list().length))
},
{ defer: true },
),
@@ -801,12 +810,14 @@ export function Prompt(props: PromptProps) {
})
const placeholderText = createMemo(() => {
- if (props.sessionID) return undefined
+ if (props.showPlaceholder === false) return undefined
if (store.mode === "shell") {
- const example = SHELL_PLACEHOLDERS[store.placeholder % SHELL_PLACEHOLDERS.length]
+ if (!shell().length) return undefined
+ const example = shell()[store.placeholder % shell().length]
return `Run a command... "${example}"`
}
- return `Ask anything... "${PLACEHOLDERS[store.placeholder % PLACEHOLDERS.length]}"`
+ if (!list().length) return undefined
+ return `Ask anything... "${list()[store.placeholder % list().length]}"`
})
const spinnerDef = createMemo(() => {
@@ -922,7 +933,7 @@ export function Prompt(props: PromptProps) {
}
}
if (e.name === "!" && input.visualCursor.offset === 0) {
- setStore("placeholder", Math.floor(Math.random() * SHELL_PLACEHOLDERS.length))
+ setStore("placeholder", randomIndex(shell().length))
setStore("mode", "shell")
e.preventDefault()
return
@@ -1097,7 +1108,7 @@ export function Prompt(props: PromptProps) {
/>
</box>
<box flexDirection="row" justifyContent="space-between">
- <Show when={status().type !== "idle"} fallback={<text />}>
+ <Show when={status().type !== "idle"} fallback={props.hint ?? <text />}>
<box
flexDirection="row"
gap={1}
diff --git a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx
index 2bfd96ac3..e5bd41b9d 100644
--- a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx
+++ b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx
@@ -14,6 +14,7 @@ import { DialogAlert } from "../ui/dialog-alert"
import { DialogConfirm } from "../ui/dialog-confirm"
import { DialogPrompt } from "../ui/dialog-prompt"
import { DialogSelect, type DialogSelectOption as SelectOption } from "../ui/dialog-select"
+import { Prompt } from "../component/prompt"
import type { useToast } from "../ui/toast"
import { Installation } from "@/installation"
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2"
@@ -287,6 +288,19 @@ export function createTuiApi(input: Input): TuiHostPluginApi {
/>
)
},
+ Prompt(props) {
+ return (
+ <Prompt
+ workspaceID={props.workspaceID}
+ visible={props.visible}
+ disabled={props.disabled}
+ onSubmit={props.onSubmit}
+ hint={props.hint}
+ showPlaceholder={props.showPlaceholder}
+ placeholders={props.placeholders}
+ />
+ )
+ },
toast(inputToast) {
input.toast.show({
title: inputToast.title,
diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
index 07549c6c2..b63bf2d2d 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
@@ -15,6 +15,10 @@ import { TuiPluginRuntime } from "../plugin"
// TODO: what is the best way to do this?
let once = false
+const placeholder = {
+ normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"],
+ shell: ["ls -la", "git status", "pwd"],
+}
export function Home() {
const sync = useSync()
@@ -49,11 +53,12 @@ export function Home() {
</box>
)
- let prompt: PromptRef
+ let prompt: PromptRef | undefined
const args = useArgs()
const local = useLocal()
onMount(() => {
if (once) return
+ if (!prompt) return
if (route.initialPrompt) {
prompt.set(route.initialPrompt)
once = true
@@ -69,6 +74,7 @@ export function Home() {
() => sync.ready && local.model.ready,
(ready) => {
if (!ready) return
+ if (!prompt) return
if (!args.prompt) return
if (prompt.current?.input !== args.prompt) return
prompt.submit()
@@ -89,14 +95,17 @@ export function Home() {
</box>
<box height={1} minHeight={0} flexShrink={1} />
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1} flexShrink={0}>
- <Prompt
- ref={(r) => {
- prompt = r
- promptRef.set(r)
- }}
- hint={Hint}
- workspaceID={route.workspaceID}
- />
+ <TuiPluginRuntime.Slot name="home_prompt" mode="replace" workspace_id={route.workspaceID}>
+ <Prompt
+ ref={(r) => {
+ prompt = r
+ promptRef.set(r)
+ }}
+ hint={Hint}
+ workspaceID={route.workspaceID}
+ placeholders={placeholder}
+ />
+ </TuiPluginRuntime.Slot>
</box>
<TuiPluginRuntime.Slot name="home_bottom" />
<box flexGrow={1} minHeight={0} />
diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts
index c982d129f..7a34877e9 100644
--- a/packages/opencode/test/fixture/tui-plugin.ts
+++ b/packages/opencode/test/fixture/tui-plugin.ts
@@ -231,6 +231,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
DialogConfirm: () => null,
DialogPrompt: () => null,
DialogSelect: () => null,
+ Prompt: () => null,
toast: () => {},
dialog: {
replace: () => {
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index 46c6900c3..96fe5cd60 100644
--- a/packages/plugin/package.json
+++ b/packages/plugin/package.json
@@ -21,8 +21,8 @@
"zod": "catalog:"
},
"peerDependencies": {
- "@opentui/core": ">=0.1.91",
- "@opentui/solid": ">=0.1.91"
+ "@opentui/core": ">=0.1.92",
+ "@opentui/solid": ">=0.1.92"
},
"peerDependenciesMeta": {
"@opentui/core": {
@@ -33,8 +33,8 @@
}
},
"devDependencies": {
- "@opentui/core": "0.1.91",
- "@opentui/solid": "0.1.91",
+ "@opentui/core": "0.1.92",
+ "@opentui/solid": "0.1.92",
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"typescript": "catalog:",
diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts
index cbb6f62b6..bbf349490 100644
--- a/packages/plugin/src/tui.ts
+++ b/packages/plugin/src/tui.ts
@@ -135,6 +135,19 @@ export type TuiDialogSelectProps<Value = unknown> = {
current?: Value
}
+export type TuiPromptProps = {
+ workspaceID?: string
+ visible?: boolean
+ disabled?: boolean
+ onSubmit?: () => void
+ hint?: JSX.Element
+ showPlaceholder?: boolean
+ placeholders?: {
+ normal?: string[]
+ shell?: string[]
+ }
+}
+
export type TuiToast = {
variant?: "info" | "success" | "warning" | "error"
title?: string
@@ -279,6 +292,9 @@ export type TuiSidebarFileItem = {
export type TuiSlotMap = {
app: {}
home_logo: {}
+ home_prompt: {
+ workspace_id?: string
+ }
home_bottom: {}
sidebar_title: {
session_id: string
@@ -386,6 +402,7 @@ export type TuiPluginApi = {
DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element
DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element
DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element
+ Prompt: (props: TuiPromptProps) => JSX.Element
toast: (input: TuiToast) => void
dialog: TuiDialogStack
}