From 14f9e21d5c3f4e853dee8ca133693dd3b915b634 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 30 Mar 2026 14:33:01 +0200 Subject: [PATCH] pluggable home footer (#20057) --- .../cmd/tui/feature-plugins/home/footer.tsx | 93 +++++++++++++++++++ .../src/cli/cmd/tui/plugin/internal.ts | 2 + .../opencode/src/cli/cmd/tui/routes/home.tsx | 61 +----------- packages/plugin/src/tui.ts | 1 + 4 files changed, 99 insertions(+), 58 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx new file mode 100644 index 0000000000..8047c26458 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx @@ -0,0 +1,93 @@ +import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui" +import { createMemo, Match, Show, Switch } from "solid-js" +import { Global } from "@/global" + +const id = "internal:home-footer" + +function Directory(props: { api: TuiPluginApi }) { + const theme = () => props.api.theme.current + const dir = createMemo(() => { + const dir = props.api.state.path.directory || process.cwd() + const out = dir.replace(Global.Path.home, "~") + const branch = props.api.state.vcs?.branch + if (branch) return out + ":" + branch + return out + }) + + return {dir()} +} + +function Mcp(props: { api: TuiPluginApi }) { + const theme = () => props.api.theme.current + const list = createMemo(() => props.api.state.mcp()) + const has = createMemo(() => list().length > 0) + const err = createMemo(() => list().some((item) => item.status === "failed")) + const count = createMemo(() => list().filter((item) => item.status === "connected").length) + + return ( + + + + + + + + + 0 ? theme().success : theme().textMuted }}>⊙ + + + {count()} MCP + + /status + + + ) +} + +function Version(props: { api: TuiPluginApi }) { + const theme = () => props.api.theme.current + + return ( + + {props.api.app.version} + + ) +} + +function View(props: { api: TuiPluginApi }) { + return ( + + + + + + + ) +} + +const tui: TuiPlugin = async (api) => { + api.slots.register({ + order: 100, + slots: { + home_footer() { + return + }, + }, + }) +} + +const plugin: TuiPluginModule & { id: string } = { + id, + tui, +} + +export default plugin diff --git a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts index 9e28bbd2e3..856ee0ebb1 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts @@ -1,3 +1,4 @@ +import HomeFooter from "../feature-plugins/home/footer" import HomeTips from "../feature-plugins/home/tips" import SidebarContext from "../feature-plugins/sidebar/context" import SidebarMcp from "../feature-plugins/sidebar/mcp" @@ -14,6 +15,7 @@ export type InternalTuiPlugin = TuiPluginModule & { } export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [ + HomeFooter, HomeTips, SidebarContext, SidebarMcp, diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index b63bf2d2df..8826df314b 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -1,15 +1,11 @@ import { Prompt, type PromptRef } from "@tui/component/prompt" -import { createEffect, createMemo, Match, on, onMount, Show, Switch } from "solid-js" -import { useTheme } from "@tui/context/theme" +import { createEffect, on, onMount } from "solid-js" import { Logo } from "../component/logo" -import { Locale } from "@/util/locale" import { useSync } from "../context/sync" import { Toast } from "../ui/toast" import { useArgs } from "../context/args" -import { useDirectory } from "../context/directory" import { useRouteData } from "@tui/context/route" import { usePromptRef } from "../context/prompt" -import { Installation } from "@/installation" import { useLocal } from "../context/local" import { TuiPluginRuntime } from "../plugin" @@ -22,37 +18,8 @@ const placeholder = { export function Home() { const sync = useSync() - const { theme } = useTheme() const route = useRouteData("home") const promptRef = usePromptRef() - const mcp = createMemo(() => Object.keys(sync.data.mcp).length > 0) - const mcpError = createMemo(() => { - return Object.values(sync.data.mcp).some((x) => x.status === "failed") - }) - - const connectedMcpCount = createMemo(() => { - return Object.values(sync.data.mcp).filter((x) => x.status === "connected").length - }) - - const Hint = ( - - 0}> - - - - mcp errors{" "} - ctrl+x s - - - {" "} - {Locale.pluralize(connectedMcpCount(), "{} mcp server", "{} mcp servers")} - - - - - - ) - let prompt: PromptRef | undefined const args = useArgs() const local = useLocal() @@ -81,7 +48,6 @@ export function Home() { }, ), ) - const directory = useDirectory() return ( <> @@ -101,7 +67,6 @@ export function Home() { prompt = r promptRef.set(r) }} - hint={Hint} workspaceID={route.workspaceID} placeholders={placeholder} /> @@ -111,28 +76,8 @@ export function Home() { - - {directory()} - - - - - - - - - 0 ? theme.success : theme.textMuted }}>⊙ - - - {connectedMcpCount()} MCP - - /status - - - - - {Installation.VERSION} - + + ) diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index bbf3494909..b082f6abe4 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -296,6 +296,7 @@ export type TuiSlotMap = { workspace_id?: string } home_bottom: {} + home_footer: {} sidebar_title: { session_id: string title: string