diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx
index 82adf8ea0b..54d3bf019e 100644
--- a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx
+++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx
@@ -1,12 +1,18 @@
import { Keybind } from "@/util/keybind"
import type { TuiPlugin, TuiPluginApi, TuiPluginStatus } from "@opencode-ai/plugin/tui"
+import { useTerminalDimensions } from "@opentui/solid"
+import { fileURLToPath } from "url"
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
-import { createMemo, createSignal } from "solid-js"
+import { createEffect, createMemo, createSignal } from "solid-js"
const id = "internal:plugin-manager"
const key = Keybind.parse("space").at(0)
function state(api: TuiPluginApi, item: TuiPluginStatus) {
+ if (!item.enabled) {
+ return disabled
+ }
+
return (
{item.active ? "active" : "inactive"}
@@ -14,21 +20,51 @@ function state(api: TuiPluginApi, item: TuiPluginStatus) {
)
}
-function row(api: TuiPluginApi, item: TuiPluginStatus): DialogSelectOption {
+function source(spec: string) {
+ if (!spec.startsWith("file://")) return
+ return fileURLToPath(spec)
+}
+
+function meta(item: TuiPluginStatus, width: number) {
+ if (item.source === "internal") {
+ if (width >= 120) return "Built-in plugin"
+ return "Built-in"
+ }
+ const next = source(item.spec)
+ if (next) return next
+ return item.spec
+}
+
+function row(api: TuiPluginApi, item: TuiPluginStatus, width: number): DialogSelectOption {
return {
title: item.id,
value: item.id,
category: item.source === "internal" ? "Internal" : "External",
- description: item.source === "internal" ? "Built-in" : item.spec,
+ description: meta(item, width),
footer: state(api, item),
disabled: item.id === id,
}
}
function View(props: { api: TuiPluginApi }) {
+ const size = useTerminalDimensions()
const [list, setList] = createSignal(props.api.plugins.list())
const [cur, setCur] = createSignal()
const [lock, setLock] = createSignal(false)
+
+ createEffect(() => {
+ const width = size().width
+ if (width >= 128) {
+ props.api.ui.dialog.setSize("xlarge")
+ return
+ }
+ if (width >= 96) {
+ props.api.ui.dialog.setSize("large")
+ return
+ }
+ props.api.ui.dialog.setSize("medium")
+ })
+
const rows = createMemo(() =>
[...list()]
.sort((a, b) => {
@@ -37,7 +73,7 @@ function View(props: { api: TuiPluginApi }) {
if (x !== y) return x - y
return a.id.localeCompare(b.id)
})
- .map((item) => row(props.api, item)),
+ .map((item) => row(props.api, item, size().width)),
)
const flip = (x: string) => {
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx
index e43d6afe56..11c43fe24c 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx
@@ -9,7 +9,7 @@ import { Selection } from "@tui/util/selection"
export function Dialog(
props: ParentProps<{
- size?: "medium" | "large"
+ size?: "medium" | "large" | "xlarge"
onClose: () => void
}>,
) {
@@ -18,6 +18,11 @@ export function Dialog(
const renderer = useRenderer()
let dismiss = false
+ const width = () => {
+ if (props.size === "xlarge") return 116
+ if (props.size === "large") return 88
+ return 60
+ }
return (
void
}[],
- size: "medium" as "medium" | "large",
+ size: "medium" as "medium" | "large" | "xlarge",
})
const renderer = useRenderer()
@@ -136,7 +141,7 @@ function init() {
get size() {
return store.size
},
- setSize(size: "medium" | "large") {
+ setSize(size: "medium" | "large" | "xlarge") {
setStore("size", size)
},
}
diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts
index bd8416a17f..33f6af9dfd 100644
--- a/packages/opencode/test/fixture/tui-plugin.ts
+++ b/packages/opencode/test/fixture/tui-plugin.ts
@@ -133,7 +133,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
set: opts.workspace?.set ?? (() => {}),
}
let depth = 0
- let size: "medium" | "large" = "medium"
+ let size: "medium" | "large" | "xlarge" = "medium"
const has = opts.theme?.has ?? (() => false)
let selected = opts.theme?.selected ?? "opencode"
const key = {
diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts
index 5083b14357..4bb3e7013b 100644
--- a/packages/plugin/src/tui.ts
+++ b/packages/plugin/src/tui.ts
@@ -75,7 +75,7 @@ export type TuiKeybindSet = {
}
export type TuiDialogProps = {
- size?: "medium" | "large"
+ size?: "medium" | "large" | "xlarge"
onClose: () => void
children?: JSX.Element
}
@@ -83,8 +83,8 @@ export type TuiDialogProps = {
export type TuiDialogStack = {
replace: (render: () => JSX.Element, onClose?: () => void) => void
clear: () => void
- setSize: (size: "medium" | "large") => void
- readonly size: "medium" | "large"
+ setSize: (size: "medium" | "large" | "xlarge") => void
+ readonly size: "medium" | "large" | "xlarge"
readonly depth: number
readonly open: boolean
}