From 10ad788d2cfa4c5cc9bae0782a9a8060f33f6202 Mon Sep 17 00:00:00 2001 From: Aarav Sharma Date: Wed, 22 Jul 2026 11:24:50 -0600 Subject: [PATCH 1/5] feat(tui): add About dialog Adds a new "About" dialog accessible from the command palette and via the `/about` slash command. The dialog shows: - Version, channel, build kind, license - Runtime (Bun/Node), platform, $TERM - Config dir, project root, cwd - Connected providers, default model, agent/MCP/plugin counts - Helpful links (docs, GitHub, issues, Discord) - `c` to copy diagnostic info to the clipboard The body is wrapped in a scrollbox with a visible scrollbar and a height clamped to a fraction of the terminal height so the dialog stays usable on small terminals. `kilo.about` is registered as a palette command alongside the other Kilo Gateway commands in `registerKiloCommands`. --- .../cli/cmd/tui/component/dialog-about.tsx | 241 ++++++++++++++++++ .../opencode/src/kilocode/kilo-commands.tsx | 13 + 2 files changed, 254 insertions(+) create mode 100644 packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx new file mode 100644 index 000000000000..84c9839901b0 --- /dev/null +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx @@ -0,0 +1,241 @@ +// kilocode_change - new file +import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core" +import { useTerminalDimensions } from "@opentui/solid" +import { createMemo, For, Show } from "solid-js" +import { InstallationVersion, InstallationChannel, InstallationBuildKind } from "@opencode-ai/core/installation/version" +import { Global } from "@opencode-ai/core/global" +import { useDialog, type DialogContext } from "@tui/ui/dialog" +import { useTheme } from "@tui/context/theme" +import { useSync } from "@tui/context/sync" +import { useProject } from "@tui/context/project" +import { useClipboard } from "@tui/context/clipboard" +import { useToast } from "@tui/ui/toast" +import { useBindings } from "@tui/keymap" +import { useTuiConfig } from "@tui/config" +import { getScrollAcceleration } from "@tui/util/scroll" + +function runtime() { + const rt = (globalThis as { Bun?: { version?: string } }).Bun + return rt?.version ? `Bun ${rt.version}` : `Node ${process.versions.node}` +} + +function arch() { + return `${process.platform}/${process.arch}` +} + +function truncate(text: string, max: number) { + return text.length > max ? text.slice(0, max - 1) + "\u2026" : text +} + +function buildDiagnostics(input: { + cwd: string + projectRoot: string + defaultModel: string + provider: string + mcp: number + plugins: number + agents: number +}) { + return [ + `Kilo CLI ${InstallationVersion} (${InstallationChannel}, ${InstallationBuildKind})`, + `Runtime: ${runtime()}`, + `Platform: ${arch()}`, + `TERM: ${process.env.TERM ?? ""}`, + `Provider: ${input.provider}`, + `Default model: ${input.defaultModel || "(none)"}`, + `MCP servers: ${input.mcp}`, + `Plugins: ${input.plugins}`, + `Agents: ${input.agents}`, + `Config: ${Global.Path.config}`, + `CWD: ${input.cwd}`, + `Project root: ${input.projectRoot}`, + ].join("\n") +} + +export function showAboutDialog(dialog: DialogContext) { + dialog.setSize("large") + dialog.replace(() => ) +} + +function Row(props: { label: string; value: string; theme: ReturnType["theme"]; muted?: boolean }) { + return ( + + + {props.label} + + + {props.value} + + + ) +} + +export function DialogAbout() { + const dialog = useDialog() + const { theme } = useTheme() + const sync = useSync() + const project = useProject() + const clipboard = useClipboard() + const toast = useToast() + const dimensions = useTerminalDimensions() + const config = useTuiConfig() + const height = createMemo(() => Math.max(8, Math.min(28, Math.floor(dimensions().height * 0.7) - 5))) + const scroll = createMemo(() => getScrollAcceleration(config)) + let box: ScrollBoxRenderable | undefined + + const provider = () => { + const connected = sync.data.provider_next.connected + return connected.length ? connected.join(", ") : "(none connected)" + } + + const defaultModel = () => sync.data.config.model ?? "" + const mcpCount = () => Object.keys(sync.data.mcp).length + const plugins = () => sync.data.config.plugin ?? [] + const agentCount = () => sync.data.agent.filter((a) => !a.hidden).length + const cwd = () => project.instance.path().directory || process.cwd() + const projectRoot = () => project.data.project.mainDir ?? project.instance.path().directory ?? process.cwd() + + const links = [ + { label: "Docs", url: "https://kilo.ai/docs" }, + { label: "GitHub", url: "https://github.com/Kilo-Org/kilocode" }, + { label: "Issues", url: "https://github.com/Kilo-Org/kilocode/issues" }, + { label: "Discord", url: "https://kilo.ai/discord" }, + ] + + const diagnostics = () => + buildDiagnostics({ + cwd: cwd(), + projectRoot: projectRoot(), + defaultModel: defaultModel(), + provider: provider(), + mcp: mcpCount(), + plugins: plugins().length, + agents: agentCount(), + }) + + const copy = () => { + if (!clipboard.write) { + toast.show({ variant: "error", message: "Clipboard not available" }) + return + } + void clipboard.write(diagnostics()).then( + () => toast.show({ variant: "success", message: "Diagnostics copied" }), + (err: unknown) => + toast.show({ + variant: "error", + message: `Copy failed: ${err instanceof Error ? err.message : String(err)}`, + }), + ) + } + + useBindings(() => ({ + bindings: [ + { key: "escape", desc: "Close about", group: "Dialog", cmd: () => dialog.clear() }, + { key: "return", desc: "Close about", group: "Dialog", cmd: () => dialog.clear() }, + { key: "c", desc: "Copy diagnostics", group: "Dialog", cmd: copy }, + { key: "pageup", desc: "Scroll up", group: "Dialog", cmd: () => box?.scrollBy(-height()) }, + { key: "pagedown", desc: "Scroll down", group: "Dialog", cmd: () => box?.scrollBy(height()) }, + ], + })) + + return ( + + + + About Kilo CLI + + dialog.clear()}> + esc + + + (box = ref)} + height={height()} + scrollAcceleration={scroll()} + verticalScrollbarOptions={{ visible: true }} + viewportOptions={{ paddingRight: 1 }} + > + + + Open source AI coding agent. Access hundreds of models through Kilo Gateway. + + + + + Identity + + + + + + + + + + Runtime + + + + + + + + + Environment + + + + + + + + + Provider + + + + + + + + + 0}> + + + Plugins + + + {(item) => ( + + {" • "} + {truncate(typeof item === "string" ? item : (item[0] ?? ""), 70)} + + )} + + + + + + + Links + + + {(link) => ( + + {" "} + {link.label}: {link.url} + + )} + + + + + + + + c copy diagnostics + + + + ) +} diff --git a/packages/opencode/src/kilocode/kilo-commands.tsx b/packages/opencode/src/kilocode/kilo-commands.tsx index 58bb3e8de262..f4f96543b5ee 100644 --- a/packages/opencode/src/kilocode/kilo-commands.tsx +++ b/packages/opencode/src/kilocode/kilo-commands.tsx @@ -20,6 +20,7 @@ import { DialogClawUpgrade } from "./components/dialog-claw-upgrade.js" import { DialogIndexing } from "./components/dialog-indexing.js" import { indexingEnabled } from "./indexing-feature" import { refreshBalance } from "./balance-refresh" +import { showAboutDialog } from "@/kilocode/cli/cmd/tui/component/dialog-about" // These types are OpenCode-internal and imported at runtime type UseSDK = any @@ -269,6 +270,18 @@ export function registerKiloCommands(useSDK: () => UseSDK) { } }, }, + + // /about command + { + name: "kilo.about", + title: "About", + desc: "Show version, environment, and diagnostic info", + category: "Kilo", + slashName: "about", + run: () => { + showAboutDialog(dialog) + }, + }, ].map((command) => ({ namespace: "palette", ...command, From 81d28281d8a144f46f8533f6db7e1c12956a8f1f Mon Sep 17 00:00:00 2001 From: Aarav Sharma Date: Wed, 22 Jul 2026 11:27:16 -0600 Subject: [PATCH 2/5] docs(changeset): add entry for TUI About dialog User-facing new feature in @kilocode/cli: About dialog reachable from the command palette and via `/about`. --- .changeset/tui-about-dialog.md | 5 +++++ packages/kilo-docs/source-links.md | 5 +++++ packages/opencode/src/kilocode/kilo-commands.tsx | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/tui-about-dialog.md diff --git a/.changeset/tui-about-dialog.md b/.changeset/tui-about-dialog.md new file mode 100644 index 000000000000..d697fe19e4e5 --- /dev/null +++ b/.changeset/tui-about-dialog.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": minor +--- + +Add an About dialog in the TUI accessible from the command palette (under "Kilo") or via the `/about` slash command. Shows version, channel, build kind, license, runtime, platform, $TERM, config path, project root, cwd, connected providers, default model, and agent/MCP/plugin counts, with helpful links and a `c` shortcut to copy diagnostics to the clipboard. \ No newline at end of file diff --git a/packages/kilo-docs/source-links.md b/packages/kilo-docs/source-links.md index 74e3dcec068f..dcc2db3e9617 100644 --- a/packages/kilo-docs/source-links.md +++ b/packages/kilo-docs/source-links.md @@ -84,6 +84,9 @@ - + +- + - - @@ -117,9 +120,11 @@ - + - + - - diff --git a/packages/opencode/src/kilocode/kilo-commands.tsx b/packages/opencode/src/kilocode/kilo-commands.tsx index f4f96543b5ee..8af6f789b129 100644 --- a/packages/opencode/src/kilocode/kilo-commands.tsx +++ b/packages/opencode/src/kilocode/kilo-commands.tsx @@ -20,7 +20,7 @@ import { DialogClawUpgrade } from "./components/dialog-claw-upgrade.js" import { DialogIndexing } from "./components/dialog-indexing.js" import { indexingEnabled } from "./indexing-feature" import { refreshBalance } from "./balance-refresh" -import { showAboutDialog } from "@/kilocode/cli/cmd/tui/component/dialog-about" +import { showAboutDialog } from "./cli/cmd/tui/component/dialog-about.js" // These types are OpenCode-internal and imported at runtime type UseSDK = any From 9112b77aab8279655c1c6880358ba388fe4a77b5 Mon Sep 17 00:00:00 2001 From: Aarav Sharma Date: Tue, 28 Jul 2026 10:12:05 -0600 Subject: [PATCH 3/5] refactor(tui): simplify About dialog Drop counts (agents/MCP/plugins), plugin list, build kind, terminal, and CWD; combine runtime+platform into a single row. Inline the copy-diagnostics payload instead of a dedicated helper. Remove License from the copied payload. --- .../cli/cmd/tui/component/dialog-about.tsx | 194 +++++------------- 1 file changed, 55 insertions(+), 139 deletions(-) diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx index 84c9839901b0..ffeafe30e4c5 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx @@ -1,8 +1,7 @@ // kilocode_change - new file -import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core" -import { useTerminalDimensions } from "@opentui/solid" -import { createMemo, For, Show } from "solid-js" -import { InstallationVersion, InstallationChannel, InstallationBuildKind } from "@opencode-ai/core/installation/version" +import { TextAttributes } from "@opentui/core" +import { For } from "solid-js" +import { InstallationVersion, InstallationChannel } from "@opencode-ai/core/installation/version" import { Global } from "@opencode-ai/core/global" import { useDialog, type DialogContext } from "@tui/ui/dialog" import { useTheme } from "@tui/context/theme" @@ -11,49 +10,17 @@ import { useProject } from "@tui/context/project" import { useClipboard } from "@tui/context/clipboard" import { useToast } from "@tui/ui/toast" import { useBindings } from "@tui/keymap" -import { useTuiConfig } from "@tui/config" -import { getScrollAcceleration } from "@tui/util/scroll" function runtime() { const rt = (globalThis as { Bun?: { version?: string } }).Bun return rt?.version ? `Bun ${rt.version}` : `Node ${process.versions.node}` } -function arch() { - return `${process.platform}/${process.arch}` -} - function truncate(text: string, max: number) { return text.length > max ? text.slice(0, max - 1) + "\u2026" : text } -function buildDiagnostics(input: { - cwd: string - projectRoot: string - defaultModel: string - provider: string - mcp: number - plugins: number - agents: number -}) { - return [ - `Kilo CLI ${InstallationVersion} (${InstallationChannel}, ${InstallationBuildKind})`, - `Runtime: ${runtime()}`, - `Platform: ${arch()}`, - `TERM: ${process.env.TERM ?? ""}`, - `Provider: ${input.provider}`, - `Default model: ${input.defaultModel || "(none)"}`, - `MCP servers: ${input.mcp}`, - `Plugins: ${input.plugins}`, - `Agents: ${input.agents}`, - `Config: ${Global.Path.config}`, - `CWD: ${input.cwd}`, - `Project root: ${input.projectRoot}`, - ].join("\n") -} - export function showAboutDialog(dialog: DialogContext) { - dialog.setSize("large") dialog.replace(() => ) } @@ -77,22 +44,12 @@ export function DialogAbout() { const project = useProject() const clipboard = useClipboard() const toast = useToast() - const dimensions = useTerminalDimensions() - const config = useTuiConfig() - const height = createMemo(() => Math.max(8, Math.min(28, Math.floor(dimensions().height * 0.7) - 5))) - const scroll = createMemo(() => getScrollAcceleration(config)) - let box: ScrollBoxRenderable | undefined - const provider = () => { + const providers = () => { const connected = sync.data.provider_next.connected return connected.length ? connected.join(", ") : "(none connected)" } - const defaultModel = () => sync.data.config.model ?? "" - const mcpCount = () => Object.keys(sync.data.mcp).length - const plugins = () => sync.data.config.plugin ?? [] - const agentCount = () => sync.data.agent.filter((a) => !a.hidden).length - const cwd = () => project.instance.path().directory || process.cwd() const projectRoot = () => project.data.project.mainDir ?? project.instance.path().directory ?? process.cwd() const links = [ @@ -102,23 +59,20 @@ export function DialogAbout() { { label: "Discord", url: "https://kilo.ai/discord" }, ] - const diagnostics = () => - buildDiagnostics({ - cwd: cwd(), - projectRoot: projectRoot(), - defaultModel: defaultModel(), - provider: provider(), - mcp: mcpCount(), - plugins: plugins().length, - agents: agentCount(), - }) - const copy = () => { if (!clipboard.write) { toast.show({ variant: "error", message: "Clipboard not available" }) return } - void clipboard.write(diagnostics()).then( + const text = [ + `Kilo CLI ${InstallationVersion} (${InstallationChannel})`, + `Runtime: ${runtime()} (${process.platform}/${process.arch})`, + `Config: ${Global.Path.config}`, + `Project: ${projectRoot()}`, + `Providers: ${providers()}`, + `Default model: ${defaultModel() || "(none)"}`, + ].join("\n") + void clipboard.write(text).then( () => toast.show({ variant: "success", message: "Diagnostics copied" }), (err: unknown) => toast.show({ @@ -133,8 +87,6 @@ export function DialogAbout() { { key: "escape", desc: "Close about", group: "Dialog", cmd: () => dialog.clear() }, { key: "return", desc: "Close about", group: "Dialog", cmd: () => dialog.clear() }, { key: "c", desc: "Copy diagnostics", group: "Dialog", cmd: copy }, - { key: "pageup", desc: "Scroll up", group: "Dialog", cmd: () => box?.scrollBy(-height()) }, - { key: "pagedown", desc: "Scroll down", group: "Dialog", cmd: () => box?.scrollBy(height()) }, ], })) @@ -148,88 +100,52 @@ export function DialogAbout() { esc - (box = ref)} - height={height()} - scrollAcceleration={scroll()} - verticalScrollbarOptions={{ visible: true }} - viewportOptions={{ paddingRight: 1 }} - > - - - Open source AI coding agent. Access hundreds of models through Kilo Gateway. + + + + Open source AI coding agent. Access hundreds of models through Kilo Gateway. + + + + + Identity + + + + - - - Identity - - - - - - - - - - Runtime - - - - - - - - - Environment - - - - - - - - - Provider - - - - - - - - - 0}> - - - Plugins + + + Runtime + + + + + + + + + Provider + + + + + + + + Links + + + {(link) => ( + + {" "} + {link.label}: {link.url} - - {(item) => ( - - {" • "} - {truncate(typeof item === "string" ? item : (item[0] ?? ""), 70)} - - )} - - - - - - - Links - - - {(link) => ( - - {" "} - {link.label}: {link.url} - - )} - - + )} + - + From e90c6ef034c5fbb725b836085d6d920d12662fba Mon Sep 17 00:00:00 2001 From: Aarav Sharma Date: Tue, 28 Jul 2026 10:56:56 -0600 Subject: [PATCH 4/5] fix(tui): drop hardcoded License row from About dialog The MIT literal could go stale and mislead in a diagnostic surface meant for accurate bug reports. Drop the row and refresh the changeset to list only the items the dialog actually shows. --- .changeset/tui-about-dialog.md | 2 +- .../src/kilocode/cli/cmd/tui/component/dialog-about.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.changeset/tui-about-dialog.md b/.changeset/tui-about-dialog.md index d697fe19e4e5..7fca9a691136 100644 --- a/.changeset/tui-about-dialog.md +++ b/.changeset/tui-about-dialog.md @@ -2,4 +2,4 @@ "@kilocode/cli": minor --- -Add an About dialog in the TUI accessible from the command palette (under "Kilo") or via the `/about` slash command. Shows version, channel, build kind, license, runtime, platform, $TERM, config path, project root, cwd, connected providers, default model, and agent/MCP/plugin counts, with helpful links and a `c` shortcut to copy diagnostics to the clipboard. \ No newline at end of file +Add an About dialog in the TUI accessible from the command palette (under "Kilo") or via the `/about` slash command. Shows version, channel, runtime/platform, config path, project root, connected providers, and default model, with helpful links (docs, GitHub, issues, Discord) and a `c` shortcut to copy diagnostics to the clipboard. diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx index ffeafe30e4c5..d4461b772a4e 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx @@ -112,7 +112,6 @@ export function DialogAbout() { - From 01891812a08c26c791310c3ca2ab45c245e8969d Mon Sep 17 00:00:00 2001 From: Aarav Sharma Date: Fri, 28 Aug 2026 10:30:08 -0600 Subject: [PATCH 5/5] feat(tui): apply review feedback to about dialog - Use live picker selection for default model via useLocal().model.current() - Run config fallback through Model.name() for consistent format - Apply privacy_mode redaction to Config/Project in both copy() and display - Use shared pathLabel helper so clipboard matches on-screen rendering - Add REDACTED_PATH constant to keep path redaction distinct from balance --- .../cli/cmd/tui/component/dialog-about.tsx | 40 +++++++++++++++---- packages/opencode/src/kilocode/pii.ts | 1 + 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx index d4461b772a4e..ea3409766910 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx @@ -1,15 +1,20 @@ // kilocode_change - new file import { TextAttributes } from "@opentui/core" -import { For } from "solid-js" +import { createMemo, For } from "solid-js" import { InstallationVersion, InstallationChannel } from "@opencode-ai/core/installation/version" import { Global } from "@opencode-ai/core/global" import { useDialog, type DialogContext } from "@tui/ui/dialog" import { useTheme } from "@tui/context/theme" import { useSync } from "@tui/context/sync" +import { useLocal } from "@tui/context/local" import { useProject } from "@tui/context/project" import { useClipboard } from "@tui/context/clipboard" import { useToast } from "@tui/ui/toast" +import { useTuiPaths } from "@tui/context/runtime" +import { abbreviateHome } from "@tui/runtime" +import * as Model from "@tui/util/model" import { useBindings } from "@tui/keymap" +import { REDACTED_PATH } from "../../../../pii" function runtime() { const rt = (globalThis as { Bun?: { version?: string } }).Bun @@ -20,6 +25,11 @@ function truncate(text: string, max: number) { return text.length > max ? text.slice(0, max - 1) + "\u2026" : text } +function pathLabel(raw: string, privacy: boolean, home: string): string { + if (privacy) return REDACTED_PATH + return truncate(abbreviateHome(raw, home), 60) +} + export function showAboutDialog(dialog: DialogContext) { dialog.replace(() => ) } @@ -41,15 +51,27 @@ export function DialogAbout() { const dialog = useDialog() const { theme } = useTheme() const sync = useSync() + const local = useLocal() const project = useProject() const clipboard = useClipboard() const toast = useToast() + const paths = useTuiPaths() + + const privacy = () => + sync.data.config.privacy_mode === true || sync.data.globalConfig.privacy_mode === true const providers = () => { const connected = sync.data.provider_next.connected return connected.length ? connected.join(", ") : "(none connected)" } - const defaultModel = () => sync.data.config.model ?? "" + const defaultModel = createMemo(() => { + const m = local.model.current() + if (m) return `${m.providerID}/${Model.name(sync.data.provider, m.providerID, m.modelID)}` + const cfg = sync.data.config.model + if (!cfg) return "" + const { providerID, modelID } = Model.parse(cfg) + return `${providerID}/${Model.name(sync.data.provider, providerID, modelID)}` + }) const projectRoot = () => project.data.project.mainDir ?? project.instance.path().directory ?? process.cwd() const links = [ @@ -64,14 +86,16 @@ export function DialogAbout() { toast.show({ variant: "error", message: "Clipboard not available" }) return } - const text = [ + const lines = [ `Kilo CLI ${InstallationVersion} (${InstallationChannel})`, `Runtime: ${runtime()} (${process.platform}/${process.arch})`, - `Config: ${Global.Path.config}`, - `Project: ${projectRoot()}`, + `Config: ${pathLabel(Global.Path.config, privacy(), paths.home)}`, + `Project: ${pathLabel(projectRoot(), privacy(), paths.home)}`, `Providers: ${providers()}`, `Default model: ${defaultModel() || "(none)"}`, - ].join("\n") + ] + if (privacy()) lines.push("(paths hidden — privacy_mode is on)") + const text = lines.join("\n") void clipboard.write(text).then( () => toast.show({ variant: "success", message: "Diagnostics copied" }), (err: unknown) => @@ -119,8 +143,8 @@ export function DialogAbout() { Runtime - - + + diff --git a/packages/opencode/src/kilocode/pii.ts b/packages/opencode/src/kilocode/pii.ts index f3d900215633..dff2941317cf 100644 --- a/packages/opencode/src/kilocode/pii.ts +++ b/packages/opencode/src/kilocode/pii.ts @@ -1 +1,2 @@ export const REDACTED_BALANCE = "•••" +export const REDACTED_PATH = "•••"